pathfinder/demo/common/src/ui.rs

427 lines
17 KiB
Rust
Raw Normal View History

// pathfinder/demo/src/ui.rs
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use crate::Options;
use nfd::Response;
use pathfinder_geometry::basic::point::Point2DI32;
use pathfinder_geometry::basic::rect::RectI32;
2019-02-26 16:48:35 -05:00
use pathfinder_gl::debug::{DebugUI, PADDING, TEXT_COLOR, WINDOW_COLOR};
use pathfinder_gl::device::{Device, Texture};
2019-02-26 18:24:39 -05:00
use pathfinder_renderer::paint::ColorU;
2019-02-12 15:40:24 -05:00
use std::f32::consts::PI;
use std::path::PathBuf;
2019-02-26 16:48:35 -05:00
const ICON_SIZE: i32 = 48;
const BUTTON_WIDTH: i32 = PADDING * 2 + ICON_SIZE;
const BUTTON_HEIGHT: i32 = PADDING * 2 + ICON_SIZE;
const BUTTON_TEXT_OFFSET: i32 = PADDING + 36;
const SWITCH_SIZE: i32 = SWITCH_HALF_SIZE * 2 + 1;
const SWITCH_HALF_SIZE: i32 = 96;
2019-02-12 15:40:24 -05:00
const SLIDER_WIDTH: i32 = 360;
const SLIDER_HEIGHT: i32 = 48;
const SLIDER_TRACK_HEIGHT: i32 = 24;
const SLIDER_KNOB_WIDTH: i32 = 12;
const SLIDER_KNOB_HEIGHT: i32 = 48;
const EFFECTS_PANEL_WIDTH: i32 = 550;
const EFFECTS_PANEL_HEIGHT: i32 = BUTTON_HEIGHT * 3 + PADDING * 4;
2019-02-25 14:33:26 -05:00
const OPEN_BUTTON_X: i32 = PADDING + (BUTTON_WIDTH + PADDING) * 1;
const SCREENSHOT_BUTTON_X: i32 = PADDING + (BUTTON_WIDTH + PADDING) * 2;
const THREE_D_SWITCH_X: i32 = PADDING + (BUTTON_WIDTH + PADDING) * 3;
const BACKGROUND_SWITCH_X: i32 = PADDING + (BUTTON_WIDTH + PADDING) * 3 + PADDING + SWITCH_SIZE;
2019-02-25 14:33:26 -05:00
const ROTATE_PANEL_X: i32 = PADDING + (BUTTON_WIDTH + PADDING) * 3 + (PADDING + SWITCH_SIZE) * 2;
2019-02-12 15:40:24 -05:00
const ROTATE_PANEL_WIDTH: i32 = SLIDER_WIDTH + PADDING * 2;
const ROTATE_PANEL_HEIGHT: i32 = PADDING * 2 + SLIDER_HEIGHT;
2019-02-26 18:24:39 -05:00
static BUTTON_ICON_COLOR: ColorU = ColorU { r: 255, g: 255, b: 255, a: 255 };
static OUTLINE_COLOR: ColorU = ColorU { r: 255, g: 255, b: 255, a: 192 };
2019-02-25 14:33:26 -05:00
static EFFECTS_PNG_NAME: &'static str = "demo-effects";
static OPEN_PNG_NAME: &'static str = "demo-open";
static ROTATE_PNG_NAME: &'static str = "demo-rotate";
static ZOOM_IN_PNG_NAME: &'static str = "demo-zoom-in";
static ZOOM_OUT_PNG_NAME: &'static str = "demo-zoom-out";
static BG_LIGHT_PNG_NAME: &'static str = "demo-bg-light";
static BG_DARK_PNG_NAME: &'static str = "demo-bg-dark";
static SCREENSHOT_PNG_NAME: &'static str = "demo-screenshot";
pub struct DemoUI {
effects_texture: Texture,
open_texture: Texture,
2019-02-12 15:40:24 -05:00
rotate_texture: Texture,
zoom_in_texture: Texture,
zoom_out_texture: Texture,
bg_light_texture: Texture,
bg_dark_texture: Texture,
2019-02-25 14:33:26 -05:00
screenshot_texture: Texture,
2019-02-12 15:40:24 -05:00
effects_panel_visible: bool,
rotate_panel_visible: bool,
pub three_d_enabled: bool,
pub dark_background_enabled: bool,
pub gamma_correction_effect_enabled: bool,
pub stem_darkening_effect_enabled: bool,
pub subpixel_aa_effect_enabled: bool,
2019-02-12 15:40:24 -05:00
pub rotation: i32,
}
impl DemoUI {
pub fn new(device: &Device, options: Options) -> DemoUI {
let effects_texture = device.create_texture_from_png(EFFECTS_PNG_NAME);
let open_texture = device.create_texture_from_png(OPEN_PNG_NAME);
let rotate_texture = device.create_texture_from_png(ROTATE_PNG_NAME);
let zoom_in_texture = device.create_texture_from_png(ZOOM_IN_PNG_NAME);
let zoom_out_texture = device.create_texture_from_png(ZOOM_OUT_PNG_NAME);
let bg_light_texture = device.create_texture_from_png(BG_LIGHT_PNG_NAME);
let bg_dark_texture = device.create_texture_from_png(BG_DARK_PNG_NAME);
2019-02-25 14:33:26 -05:00
let screenshot_texture = device.create_texture_from_png(SCREENSHOT_PNG_NAME);
DemoUI {
effects_texture,
open_texture,
2019-02-12 15:40:24 -05:00
rotate_texture,
zoom_in_texture,
zoom_out_texture,
bg_light_texture,
bg_dark_texture,
2019-02-25 14:33:26 -05:00
screenshot_texture,
2019-02-26 18:24:39 -05:00
2019-02-12 15:40:24 -05:00
effects_panel_visible: false,
rotate_panel_visible: false,
2019-02-26 18:24:39 -05:00
three_d_enabled: options.three_d,
dark_background_enabled: true,
gamma_correction_effect_enabled: false,
stem_darkening_effect_enabled: false,
subpixel_aa_effect_enabled: false,
2019-02-12 15:40:24 -05:00
rotation: SLIDER_WIDTH / 2,
}
}
fn rotation(&self) -> f32 {
2019-02-12 15:40:24 -05:00
(self.rotation as f32 / SLIDER_WIDTH as f32 * 2.0 - 1.0) * PI
}
pub fn update(&mut self, debug_ui: &mut DebugUI, event: &mut UIEvent, action: &mut UIAction) {
let bottom = debug_ui.framebuffer_size().y() - PADDING;
// Draw effects button.
let effects_button_position = Point2DI32::new(PADDING, bottom - BUTTON_HEIGHT);
if self.draw_button(debug_ui, event, effects_button_position, &self.effects_texture) {
2019-02-12 15:40:24 -05:00
self.effects_panel_visible = !self.effects_panel_visible;
}
// Draw open button.
2019-02-25 14:33:26 -05:00
let lower_button_y = bottom - BUTTON_HEIGHT;
let open_button_position = Point2DI32::new(OPEN_BUTTON_X, lower_button_y);
if self.draw_button(debug_ui, event, open_button_position, &self.open_texture) {
if let Ok(Response::Okay(file)) = nfd::open_file_dialog(Some("svg"), None) {
*action = UIAction::OpenFile(PathBuf::from(file));
}
}
2019-02-25 14:33:26 -05:00
// Draw screenshot button.
let screenshot_button_position = Point2DI32::new(SCREENSHOT_BUTTON_X, lower_button_y);
if self.draw_button(debug_ui,
event,
screenshot_button_position,
&self.screenshot_texture) {
if let Ok(Response::Okay(file)) = nfd::open_save_dialog(Some("png"), None) {
*action = UIAction::TakeScreenshot(PathBuf::from(file));
}
}
// Draw 3D switch.
2019-02-25 14:33:26 -05:00
let three_d_switch_origin = Point2DI32::new(THREE_D_SWITCH_X, lower_button_y);
self.three_d_enabled = self.draw_text_switch(debug_ui,
event,
2019-02-25 14:33:26 -05:00
three_d_switch_origin,
"2D",
"3D",
self.three_d_enabled);
// Draw background switch.
2019-02-25 14:33:26 -05:00
let background_switch_origin = Point2DI32::new(BACKGROUND_SWITCH_X, lower_button_y);
self.dark_background_enabled = self.draw_image_switch(debug_ui,
event,
background_switch_origin,
&self.bg_light_texture,
&self.bg_dark_texture,
self.dark_background_enabled);
// Draw rotate and zoom buttons, if applicable.
if !self.three_d_enabled {
2019-02-12 15:40:24 -05:00
let rotate_button_y = bottom - BUTTON_HEIGHT;
let rotate_button_position = Point2DI32::new(ROTATE_PANEL_X, rotate_button_y);
if self.draw_button(debug_ui, event, rotate_button_position, &self.rotate_texture) {
self.rotate_panel_visible = !self.rotate_panel_visible;
}
let zoom_in_button_x = ROTATE_PANEL_X + BUTTON_WIDTH + PADDING;
let zoom_in_button_position = Point2DI32::new(zoom_in_button_x, rotate_button_y);
if self.draw_button(debug_ui, event, zoom_in_button_position, &self.zoom_in_texture) {
*action = UIAction::ZoomIn;
}
let zoom_out_button_x = ROTATE_PANEL_X + (BUTTON_WIDTH + PADDING) * 2;
let zoom_out_button_position = Point2DI32::new(zoom_out_button_x, rotate_button_y);
if self.draw_button(debug_ui,
event,
zoom_out_button_position,
&self.zoom_out_texture) {
*action = UIAction::ZoomOut;
}
2019-02-12 15:40:24 -05:00
}
// Draw effects panel, if necessary.
self.draw_effects_panel(debug_ui, event);
// Draw rotate panel, if necessary.
self.draw_rotate_panel(debug_ui, event, action);
}
2019-02-12 15:40:24 -05:00
fn draw_effects_panel(&mut self, debug_ui: &mut DebugUI, event: &mut UIEvent) {
if !self.effects_panel_visible {
return;
}
let bottom = debug_ui.framebuffer_size().y() - PADDING;
2019-02-12 15:40:24 -05:00
let effects_panel_y = bottom - (BUTTON_HEIGHT + PADDING + EFFECTS_PANEL_HEIGHT);
2019-02-26 16:48:35 -05:00
debug_ui.draw_solid_rounded_rect(RectI32::new(Point2DI32::new(PADDING, effects_panel_y),
Point2DI32::new(EFFECTS_PANEL_WIDTH,
EFFECTS_PANEL_HEIGHT)),
WINDOW_COLOR);
self.gamma_correction_effect_enabled =
self.draw_effects_switch(debug_ui,
event,
"Gamma Correction",
0,
2019-02-12 15:40:24 -05:00
effects_panel_y,
self.gamma_correction_effect_enabled);
self.stem_darkening_effect_enabled =
self.draw_effects_switch(debug_ui,
event,
"Stem Darkening",
1,
2019-02-12 15:40:24 -05:00
effects_panel_y,
self.stem_darkening_effect_enabled);
self.subpixel_aa_effect_enabled =
self.draw_effects_switch(debug_ui,
event,
"Subpixel AA",
2,
2019-02-12 15:40:24 -05:00
effects_panel_y,
self.subpixel_aa_effect_enabled);
}
fn draw_rotate_panel(&mut self,
debug_ui: &mut DebugUI,
event: &mut UIEvent,
action: &mut UIAction) {
2019-02-12 15:40:24 -05:00
if !self.rotate_panel_visible {
return;
}
let bottom = debug_ui.framebuffer_size().y() - PADDING;
2019-02-12 15:40:24 -05:00
let rotate_panel_y = bottom - (BUTTON_HEIGHT + PADDING + ROTATE_PANEL_HEIGHT);
2019-02-26 16:48:35 -05:00
let rotate_panel_origin = Point2DI32::new(ROTATE_PANEL_X, rotate_panel_y);
let rotate_panel_size = Point2DI32::new(ROTATE_PANEL_WIDTH, ROTATE_PANEL_HEIGHT);
debug_ui.draw_solid_rounded_rect(RectI32::new(rotate_panel_origin, rotate_panel_size),
WINDOW_COLOR);
2019-02-12 15:40:24 -05:00
let (widget_x, widget_y) = (ROTATE_PANEL_X + PADDING, rotate_panel_y + PADDING);
let widget_rect = RectI32::new(Point2DI32::new(widget_x, widget_y),
Point2DI32::new(SLIDER_WIDTH, SLIDER_KNOB_HEIGHT));
if let Some(position) = event.handle_mouse_down_or_dragged_in_rect(widget_rect) {
2019-02-12 15:40:24 -05:00
self.rotation = position.x();
*action = UIAction::Rotate(self.rotation());
2019-02-12 15:40:24 -05:00
}
let slider_track_y = rotate_panel_y + PADDING + SLIDER_KNOB_HEIGHT / 2 -
SLIDER_TRACK_HEIGHT / 2;
let slider_track_rect =
RectI32::new(Point2DI32::new(widget_x, slider_track_y),
Point2DI32::new(SLIDER_WIDTH, SLIDER_TRACK_HEIGHT));
debug_ui.draw_rect_outline(slider_track_rect, TEXT_COLOR);
let slider_knob_x = widget_x + self.rotation - SLIDER_KNOB_WIDTH / 2;
let slider_knob_rect =
RectI32::new(Point2DI32::new(slider_knob_x, widget_y),
Point2DI32::new(SLIDER_KNOB_WIDTH, SLIDER_KNOB_HEIGHT));
debug_ui.draw_solid_rect(slider_knob_rect, TEXT_COLOR);
}
fn draw_button(&self,
debug_ui: &mut DebugUI,
event: &mut UIEvent,
origin: Point2DI32,
texture: &Texture)
-> bool {
let button_rect = RectI32::new(origin, Point2DI32::new(BUTTON_WIDTH, BUTTON_HEIGHT));
2019-02-26 16:48:35 -05:00
debug_ui.draw_solid_rounded_rect(button_rect, WINDOW_COLOR);
2019-02-26 18:24:39 -05:00
debug_ui.draw_rounded_rect_outline(button_rect, OUTLINE_COLOR);
debug_ui.draw_texture(origin + Point2DI32::new(PADDING, PADDING),
texture,
BUTTON_ICON_COLOR);
2019-02-12 15:40:24 -05:00
event.handle_mouse_down_in_rect(button_rect).is_some()
}
fn draw_effects_switch(&self,
debug_ui: &mut DebugUI,
event: &mut UIEvent,
text: &str,
index: i32,
window_y: i32,
value: bool)
-> bool {
let text_x = PADDING * 2;
let text_y = window_y + PADDING + BUTTON_TEXT_OFFSET + (BUTTON_HEIGHT + PADDING) * index;
debug_ui.draw_text(text, Point2DI32::new(text_x, text_y), false);
2019-02-12 15:40:24 -05:00
let switch_x = PADDING + EFFECTS_PANEL_WIDTH - (SWITCH_SIZE + PADDING);
let switch_y = window_y + PADDING + (BUTTON_HEIGHT + PADDING) * index;
self.draw_text_switch(debug_ui,
event,
Point2DI32::new(switch_x, switch_y),
"Off",
"On",
value)
}
fn draw_text_switch(&self,
debug_ui: &mut DebugUI,
event: &mut UIEvent,
origin: Point2DI32,
off_text: &str,
on_text: &str,
mut value: bool)
-> bool {
value = self.draw_switch(debug_ui, event, origin, value);
let off_size = debug_ui.measure_text(off_text);
let on_size = debug_ui.measure_text(on_text);
let off_offset = SWITCH_HALF_SIZE / 2 - off_size / 2;
let on_offset = SWITCH_HALF_SIZE + SWITCH_HALF_SIZE / 2 - on_size / 2;
let text_top = BUTTON_TEXT_OFFSET;
debug_ui.draw_text(off_text, origin + Point2DI32::new(off_offset, text_top), !value);
debug_ui.draw_text(on_text, origin + Point2DI32::new(on_offset, text_top), value);
value
}
fn draw_image_switch(&self,
debug_ui: &mut DebugUI,
event: &mut UIEvent,
origin: Point2DI32,
off_texture: &Texture,
on_texture: &Texture,
mut value: bool)
-> bool {
value = self.draw_switch(debug_ui, event, origin, value);
let off_offset = SWITCH_HALF_SIZE / 2 - off_texture.size.x() / 2;
let on_offset = SWITCH_HALF_SIZE + SWITCH_HALF_SIZE / 2 - on_texture.size.x() / 2;
let off_color = if !value { WINDOW_COLOR } else { TEXT_COLOR };
let on_color = if value { WINDOW_COLOR } else { TEXT_COLOR };
debug_ui.draw_texture(origin + Point2DI32::new(off_offset, PADDING),
off_texture,
off_color);
debug_ui.draw_texture(origin + Point2DI32::new(on_offset, PADDING),
on_texture,
on_color);
value
}
fn draw_switch(&self,
debug_ui: &mut DebugUI,
event: &mut UIEvent,
origin: Point2DI32,
mut value: bool)
-> bool {
let widget_rect = RectI32::new(origin, Point2DI32::new(SWITCH_SIZE, BUTTON_HEIGHT));
2019-02-12 15:40:24 -05:00
if event.handle_mouse_down_in_rect(widget_rect).is_some() {
value = !value;
}
2019-02-26 16:48:35 -05:00
debug_ui.draw_solid_rounded_rect(widget_rect, WINDOW_COLOR);
2019-02-26 18:24:39 -05:00
debug_ui.draw_rounded_rect_outline(widget_rect, OUTLINE_COLOR);
let highlight_size = Point2DI32::new(SWITCH_HALF_SIZE, BUTTON_HEIGHT);
if !value {
2019-02-26 16:48:35 -05:00
debug_ui.draw_solid_rounded_rect(RectI32::new(origin, highlight_size), TEXT_COLOR);
} else {
let x_offset = SWITCH_HALF_SIZE + 1;
2019-02-26 16:48:35 -05:00
debug_ui.draw_solid_rounded_rect(RectI32::new(origin + Point2DI32::new(x_offset, 0),
highlight_size),
TEXT_COLOR);
}
value
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum UIAction {
None,
OpenFile(PathBuf),
2019-02-25 14:33:26 -05:00
TakeScreenshot(PathBuf),
ZoomIn,
ZoomOut,
Rotate(f32),
}
pub enum UIEvent {
None,
MouseDown(Point2DI32),
MouseDragged {
absolute_position: Point2DI32,
relative_position: Point2DI32,
}
}
impl UIEvent {
pub fn is_none(&self) -> bool {
match *self { UIEvent::None => true, _ => false }
}
2019-02-12 15:40:24 -05:00
fn handle_mouse_down_in_rect(&mut self, rect: RectI32) -> Option<Point2DI32> {
if let UIEvent::MouseDown(point) = *self {
if rect.contains_point(point) {
*self = UIEvent::None;
2019-02-12 15:40:24 -05:00
return Some(point - rect.origin());
}
}
2019-02-12 15:40:24 -05:00
None
}
fn handle_mouse_down_or_dragged_in_rect(&mut self, rect: RectI32) -> Option<Point2DI32> {
match *self {
UIEvent::MouseDown(point) | UIEvent::MouseDragged { absolute_position: point, .. }
if rect.contains_point(point) => {
*self = UIEvent::None;
Some(point - rect.origin())
}
_ => None,
}
}
}