Add gui for layout options

This commit is contained in:
Emil Ernerfeldt 2018-12-26 23:08:50 +01:00
parent e54f46035b
commit d828e58d1b
7 changed files with 168 additions and 77 deletions

View file

@ -1,4 +1,8 @@
use crate::{gui::Gui, math::*, types::*}; use crate::{layout::Layout, math::*, types::*};
pub trait GuiSettings {
fn show_gui(&mut self, gui: &mut Layout);
}
pub struct App { pub struct App {
checked: bool, checked: bool,
@ -25,8 +29,8 @@ impl Default for App {
} }
} }
impl App { impl GuiSettings for App {
pub fn show_gui(&mut self, gui: &mut Gui) { fn show_gui(&mut self, gui: &mut Layout) {
gui.checkbox("checkbox", &mut self.checked); gui.checkbox("checkbox", &mut self.checked);
if gui if gui
@ -71,7 +75,28 @@ impl App {
}), }),
}])); }]));
let commands_json = format!("{:#?}", gui.gui_commands()); gui.label("LayoutOptions:");
gui.label(format!("All gui commands: {}", commands_json)); let mut layout_options = gui.layout_options;
layout_options.show_gui(gui);
gui.layout_options = layout_options;
}
}
impl GuiSettings for crate::layout::LayoutOptions {
fn show_gui(&mut self, gui: &mut Layout) {
if gui.button("Reset LayoutOptions").clicked {
*self = Default::default();
}
gui.slider_f32("item_spacing.x", &mut self.item_spacing.x, 0.0, 10.0);
gui.slider_f32("item_spacing.y", &mut self.item_spacing.y, 0.0, 10.0);
gui.slider_f32("width", &mut self.width, 0.0, 1000.0);
gui.slider_f32("button_height", &mut self.button_height, 0.0, 60.0);
gui.slider_f32(
"checkbox_radio_height",
&mut self.checkbox_radio_height,
0.0,
60.0,
);
gui.slider_f32("slider_height", &mut self.slider_height, 0.0, 60.0);
} }
} }

28
src/emgui.rs Normal file
View file

@ -0,0 +1,28 @@
use crate::{layout, math::*, style, types::*};
/// Encapsulates input, layout and painting for ease of use.
#[derive(Clone, Debug, Default)]
pub struct Emgui {
pub last_input: RawInput,
pub layout: layout::Layout,
pub style: style::Style,
}
impl Emgui {
pub fn new_frame(&mut self, new_input: RawInput) {
let gui_input = GuiInput::from_last_and_new(&self.last_input, &new_input);
self.last_input = new_input;
// TODO: this should be nicer
self.layout.commands.clear();
self.layout.cursor = vec2(32.0, 32.0);
self.layout.input = gui_input;
if !gui_input.mouse_down {
self.layout.state.active_id = None;
}
}
pub fn paint(&mut self) -> Vec<PaintCmd> {
style::into_paint_commands(self.layout.gui_commands(), &self.style)
}
}

View file

@ -1,24 +1,59 @@
use crate::{math::*, types::*}; use crate::{math::*, types::*};
// TODO: implement Gui on this so we can add children to a widget // ----------------------------------------------------------------------------
// pub struct Widget {}
type Id = u64; #[derive(Clone, Copy, Debug, Serialize)]
pub struct LayoutOptions {
// Horizontal and vertical spacing between widgets
pub item_spacing: Vec2,
/// Default width of buttons, sliders etc
pub width: f32,
/// Height of a button
pub button_height: f32,
/// Height of a checkbox and radio button
pub checkbox_radio_height: f32,
/// Height of a slider
pub slider_height: f32,
}
impl Default for LayoutOptions {
fn default() -> Self {
LayoutOptions {
item_spacing: Vec2 { x: 8.0, y: 4.0 },
width: 200.0,
button_height: 24.0,
checkbox_radio_height: 24.0,
slider_height: 32.0,
}
}
}
// ----------------------------------------------------------------------------
#[derive(Clone, Copy, Debug, Default)] #[derive(Clone, Copy, Debug, Default)]
pub struct GuiState { pub struct State {
/// The widget being interacted with (e.g. dragged, in case of a slider). /// The widget being interacted with (e.g. dragged, in case of a slider).
pub active_id: Option<Id>, pub active_id: Option<Id>,
} }
pub struct Gui { // ----------------------------------------------------------------------------
type Id = u64;
#[derive(Clone, Debug, Default)]
pub struct Layout {
pub commands: Vec<GuiCmd>, pub commands: Vec<GuiCmd>,
pub cursor: Vec2, pub cursor: Vec2,
pub input: GuiInput, pub input: GuiInput,
pub state: GuiState, pub layout_options: LayoutOptions,
pub state: State,
} }
impl Gui { impl Layout {
pub fn input(&self) -> &GuiInput { pub fn input(&self) -> &GuiInput {
&self.input &self.input
} }
@ -34,7 +69,10 @@ impl Gui {
let id = self.get_id(&text); let id = self.get_id(&text);
let rect = Rect { let rect = Rect {
pos: self.cursor, pos: self.cursor,
size: Vec2 { x: 176.0, y: 24.0 }, // TODO: get from some settings size: Vec2 {
x: self.layout_options.width,
y: self.layout_options.button_height,
},
}; };
let interact = self.interactive_rect(id, &rect); let interact = self.interactive_rect(id, &rect);
@ -45,7 +83,7 @@ impl Gui {
text, text,
}); });
self.cursor.y += rect.size.y + 16.0; self.cursor.y += rect.size.y + self.layout_options.item_spacing.y;
interact interact
} }
@ -54,7 +92,10 @@ impl Gui {
let id = self.get_id(&label); let id = self.get_id(&label);
let rect = Rect { let rect = Rect {
pos: self.cursor, pos: self.cursor,
size: Vec2 { x: 200.0, y: 24.0 }, // TODO: get from some settings size: Vec2 {
x: self.layout_options.width,
y: self.layout_options.checkbox_radio_height,
},
}; };
let interact = self.interactive_rect(id, &rect); let interact = self.interactive_rect(id, &rect);
@ -69,7 +110,7 @@ impl Gui {
text: label, text: label,
}); });
self.cursor.y += rect.size.y + 16.0; self.cursor.y += rect.size.y + self.layout_options.item_spacing.y;
interact interact
} }
@ -79,7 +120,7 @@ impl Gui {
self.text(self.cursor, TextStyle::Label, line); self.text(self.cursor, TextStyle::Label, line);
self.cursor.y += 16.0; self.cursor.y += 16.0;
} }
self.cursor.y += 16.0; // Padding self.cursor.y += self.layout_options.item_spacing.y;
} }
/// A radio button /// A radio button
@ -88,7 +129,10 @@ impl Gui {
let id = self.get_id(&label); let id = self.get_id(&label);
let rect = Rect { let rect = Rect {
pos: self.cursor, pos: self.cursor,
size: Vec2 { x: 200.0, y: 24.0 }, // TODO: get from some settings size: Vec2 {
x: self.layout_options.width,
y: self.layout_options.checkbox_radio_height,
},
}; };
let interact = self.interactive_rect(id, &rect); let interact = self.interactive_rect(id, &rect);
@ -100,7 +144,7 @@ impl Gui {
text: label, text: label,
}); });
self.cursor.y += rect.size.y + 16.0; self.cursor.y += rect.size.y + self.layout_options.item_spacing.y;
interact interact
} }
@ -115,7 +159,10 @@ impl Gui {
let id = self.get_id(&label); let id = self.get_id(&label);
let rect = Rect { let rect = Rect {
pos: self.cursor, pos: self.cursor,
size: Vec2 { x: 200.0, y: 24.0 }, // TODO: get from some settings size: Vec2 {
x: self.layout_options.width,
y: self.layout_options.slider_height,
},
}; };
let interact = self.interactive_rect(id, &rect); let interact = self.interactive_rect(id, &rect);
@ -134,7 +181,7 @@ impl Gui {
value: *value, value: *value,
}); });
self.cursor.y += rect.size.y + 16.0; self.cursor.y += rect.size.y + self.layout_options.item_spacing.y;
interact interact
} }

View file

@ -13,16 +13,16 @@ use std::sync::Mutex;
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
use crate::{math::Vec2, types::*}; use crate::types::*;
pub mod app; pub mod app;
pub mod gui; pub mod emgui;
pub mod layout;
pub mod math; pub mod math;
pub mod style; pub mod style;
pub mod types; pub mod types;
/* /*
// Fast compilation, slow code: // Fast compilation, slow code:
fn foo(x: &dyn Trait); fn foo(x: &dyn Trait);
@ -43,26 +43,15 @@ pub fn show_gui(raw_input_json: &str) -> String {
lazy_static::lazy_static! { lazy_static::lazy_static! {
static ref APP: Mutex<app::App> = Default::default(); static ref APP: Mutex<app::App> = Default::default();
static ref LAST_INPUT: Mutex<RawInput> = Default::default(); static ref EMGUI: Mutex<crate::emgui::Emgui> = Default::default();
static ref GUI_STATE: Mutex<gui::GuiState> = Default::default();
} }
let gui_input = GuiInput::from_last_and_new(&LAST_INPUT.lock().unwrap(), &raw_input); let mut emgui = EMGUI.lock().unwrap();
*LAST_INPUT.lock().unwrap() = raw_input; emgui.new_frame(raw_input);
let mut gui = gui::Gui { use crate::app::GuiSettings;
commands: Vec::new(), APP.lock().unwrap().show_gui(&mut emgui.layout);
cursor: Vec2 { x: 32.0, y: 32.0 },
input: gui_input,
state: *GUI_STATE.lock().unwrap(),
};
if !gui_input.mouse_down {
gui.state.active_id = None;
}
APP.lock().unwrap().show_gui(&mut gui);
*GUI_STATE.lock().unwrap() = gui.state; let commands = emgui.paint();
let commands = style::into_paint_commands(gui.gui_commands());
serde_json::to_string(&commands).unwrap() serde_json::to_string(&commands).unwrap()
} }

View file

@ -45,6 +45,10 @@ pub struct Rect {
} }
impl Rect { impl Rect {
pub fn from_min_size(min: Vec2, size: Vec2) -> Self {
Rect { pos: min, size }
}
pub fn from_center_size(center: Vec2, size: Vec2) -> Self { pub fn from_center_size(center: Vec2, size: Vec2) -> Self {
Rect { Rect {
pos: center - size * 0.5, pos: center - size * 0.5,
@ -74,7 +78,7 @@ impl Rect {
} }
} }
pub fn lerp(t: f32, min: f32, max: f32) -> f32 { pub fn lerp(min: f32, max: f32, t: f32) -> f32 {
(1.0 - t) * min + t * max (1.0 - t) * min + t * max
} }
@ -86,5 +90,5 @@ pub fn remap_clamp(from: f32, from_min: f32, from_max: f32, to_min: f32, to_max:
} else { } else {
(from - from_min) / (from_max - from_min) (from - from_min) / (from_max - from_min)
}; };
lerp(t, to_min, to_max) lerp(to_min, to_max, t)
} }

View file

@ -1,7 +1,19 @@
use crate::{math::*, types::*}; use crate::{math::*, types::*};
#[derive(Clone, Copy, Debug)]
pub struct Style {
/// For stuff like checkmarks in check boxes
pub line_width: f32,
}
impl Default for Style {
fn default() -> Style {
Style { line_width: 2.0 }
}
}
/// TODO: a Style struct which defines colors etc /// TODO: a Style struct which defines colors etc
fn translate_cmd(out_commands: &mut Vec<PaintCmd>, cmd: GuiCmd) { fn translate_cmd(out_commands: &mut Vec<PaintCmd>, style: &Style, cmd: GuiCmd) {
match cmd { match cmd {
GuiCmd::PaintCommands(mut commands) => out_commands.append(&mut commands), GuiCmd::PaintCommands(mut commands) => out_commands.append(&mut commands),
GuiCmd::Button { GuiCmd::Button {
@ -29,7 +41,7 @@ fn translate_cmd(out_commands: &mut Vec<PaintCmd>, cmd: GuiCmd) {
font: "14px Palatino".to_string(), font: "14px Palatino".to_string(),
pos: Vec2 { pos: Vec2 {
x: rect.center().x, x: rect.center().x,
y: rect.center().y + 14.0 / 2.0, y: rect.center().y + 6.0,
}, },
text, text,
text_align: TextAlign::Center, text_align: TextAlign::Center,
@ -79,7 +91,7 @@ fn translate_cmd(out_commands: &mut Vec<PaintCmd>, cmd: GuiCmd) {
vec2(smaller_rect.max().x, smaller_rect.min().y), vec2(smaller_rect.max().x, smaller_rect.min().y),
], ],
style: stroke_style.clone(), style: stroke_style.clone(),
width: 4.0, width: style.line_width,
}); });
} }
@ -88,7 +100,7 @@ fn translate_cmd(out_commands: &mut Vec<PaintCmd>, cmd: GuiCmd) {
font: "14px Palatino".to_string(), font: "14px Palatino".to_string(),
pos: Vec2 { pos: Vec2 {
x: box_rect.max().x + 4.0, x: box_rect.max().x + 4.0,
y: rect.center().y + 14.0 / 2.0, y: rect.center().y + 5.0,
}, },
text, text,
text_align: TextAlign::Start, text_align: TextAlign::Start,
@ -153,12 +165,17 @@ fn translate_cmd(out_commands: &mut Vec<PaintCmd>, cmd: GuiCmd) {
rect, rect,
value, value,
} => { } => {
let thin_rect = Rect::from_center_size(rect.center(), vec2(rect.size.x, 8.0)); let thin_rect = Rect::from_min_size(
vec2(rect.min().x, lerp(rect.min().y, rect.max().y, 2.0 / 3.0)),
vec2(rect.size.x, 8.0),
);
let marker_center_x = remap_clamp(value, min, max, rect.min().x, rect.max().x); let marker_center_x = remap_clamp(value, min, max, rect.min().x, rect.max().x);
let marker_rect = let marker_rect = Rect::from_center_size(
Rect::from_center_size(vec2(marker_center_x, rect.center().y), vec2(16.0, 16.0)); vec2(marker_center_x, thin_rect.center().y),
vec2(16.0, 16.0),
);
let marker_fill_style = if interact.active { let marker_fill_style = if interact.active {
"#888888ff".to_string() "#888888ff".to_string()
@ -187,7 +204,10 @@ fn translate_cmd(out_commands: &mut Vec<PaintCmd>, cmd: GuiCmd) {
out_commands.push(PaintCmd::Text { out_commands.push(PaintCmd::Text {
fill_style: "#ffffffbb".to_string(), fill_style: "#ffffffbb".to_string(),
font: "14px Palatino".to_string(), font: "14px Palatino".to_string(),
pos: rect.min(), pos: vec2(
rect.min().x,
lerp(rect.min().y, rect.max().y, 1.0 / 3.0) + 6.0,
),
text: format!("{}: {:.3}", label, value), text: format!("{}: {:.3}", label, value),
text_align: TextAlign::Start, text_align: TextAlign::Start,
}); });
@ -204,7 +224,7 @@ fn translate_cmd(out_commands: &mut Vec<PaintCmd>, cmd: GuiCmd) {
out_commands.push(PaintCmd::Text { out_commands.push(PaintCmd::Text {
fill_style, fill_style,
font: "14px Palatino".to_string(), font: "14px Palatino".to_string(),
pos, pos: pos + vec2(0.0, 7.0), // TODO: FIXME
text, text,
text_align, text_align,
}); });
@ -212,10 +232,10 @@ fn translate_cmd(out_commands: &mut Vec<PaintCmd>, cmd: GuiCmd) {
} }
} }
pub fn into_paint_commands(gui_commands: &[GuiCmd]) -> Vec<PaintCmd> { pub fn into_paint_commands(gui_commands: &[GuiCmd], style: &Style) -> Vec<PaintCmd> {
let mut paint_commands = vec![]; let mut paint_commands = vec![];
for gui_cmd in gui_commands { for gui_cmd in gui_commands {
translate_cmd(&mut paint_commands, gui_cmd.clone()) translate_cmd(&mut paint_commands, style, gui_cmd.clone())
} }
paint_commands paint_commands
} }

View file

@ -16,7 +16,7 @@ pub struct RawInput {
} }
/// What the gui maintains /// What the gui maintains
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug, Default)]
pub struct GuiInput { pub struct GuiInput {
/// Is the button currently down? /// Is the button currently down?
pub mouse_down: bool, pub mouse_down: bool,
@ -48,28 +48,6 @@ impl GuiInput {
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
/// Names taken from Dear ImGui
#[derive(Clone, Copy, Debug, Default, Serialize)]
pub struct LayoutOptions {
// Horizontal and vertical spacing between widgets
item_spacing: Vec2,
/// Padding within a framed rectangle (used by most widgets)
frame_padding: Vec2,
}
impl LayoutOptions {
pub fn new() -> Self {
// Values taken from Dear ImGui
LayoutOptions {
item_spacing: Vec2 { x: 8.0, y: 4.0 },
frame_padding: Vec2 { x: 4.0, y: 3.0 },
}
}
}
// ----------------------------------------------------------------------------
#[derive(Clone, Copy, Debug, Default, Serialize)] #[derive(Clone, Copy, Debug, Default, Serialize)]
pub struct InteractInfo { pub struct InteractInfo {
pub hovered: bool, pub hovered: bool,