Add gui for layout options
This commit is contained in:
parent
e54f46035b
commit
d828e58d1b
7 changed files with 168 additions and 77 deletions
35
src/app.rs
35
src/app.rs
|
@ -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 {
|
||||
checked: bool,
|
||||
|
@ -25,8 +29,8 @@ impl Default for App {
|
|||
}
|
||||
}
|
||||
|
||||
impl App {
|
||||
pub fn show_gui(&mut self, gui: &mut Gui) {
|
||||
impl GuiSettings for App {
|
||||
fn show_gui(&mut self, gui: &mut Layout) {
|
||||
gui.checkbox("checkbox", &mut self.checked);
|
||||
|
||||
if gui
|
||||
|
@ -71,7 +75,28 @@ impl App {
|
|||
}),
|
||||
}]));
|
||||
|
||||
let commands_json = format!("{:#?}", gui.gui_commands());
|
||||
gui.label(format!("All gui commands: {}", commands_json));
|
||||
gui.label("LayoutOptions:");
|
||||
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
28
src/emgui.rs
Normal 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)
|
||||
}
|
||||
}
|
|
@ -1,24 +1,59 @@
|
|||
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)]
|
||||
pub struct GuiState {
|
||||
pub struct State {
|
||||
/// The widget being interacted with (e.g. dragged, in case of a slider).
|
||||
pub active_id: Option<Id>,
|
||||
}
|
||||
|
||||
pub struct Gui {
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
type Id = u64;
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct Layout {
|
||||
pub commands: Vec<GuiCmd>,
|
||||
pub cursor: Vec2,
|
||||
pub input: GuiInput,
|
||||
pub state: GuiState,
|
||||
pub layout_options: LayoutOptions,
|
||||
pub state: State,
|
||||
}
|
||||
|
||||
impl Gui {
|
||||
impl Layout {
|
||||
pub fn input(&self) -> &GuiInput {
|
||||
&self.input
|
||||
}
|
||||
|
@ -34,7 +69,10 @@ impl Gui {
|
|||
let id = self.get_id(&text);
|
||||
let rect = Rect {
|
||||
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);
|
||||
|
@ -45,7 +83,7 @@ impl Gui {
|
|||
text,
|
||||
});
|
||||
|
||||
self.cursor.y += rect.size.y + 16.0;
|
||||
self.cursor.y += rect.size.y + self.layout_options.item_spacing.y;
|
||||
interact
|
||||
}
|
||||
|
||||
|
@ -54,7 +92,10 @@ impl Gui {
|
|||
let id = self.get_id(&label);
|
||||
let rect = Rect {
|
||||
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);
|
||||
|
@ -69,7 +110,7 @@ impl Gui {
|
|||
text: label,
|
||||
});
|
||||
|
||||
self.cursor.y += rect.size.y + 16.0;
|
||||
self.cursor.y += rect.size.y + self.layout_options.item_spacing.y;
|
||||
interact
|
||||
}
|
||||
|
||||
|
@ -79,7 +120,7 @@ impl Gui {
|
|||
self.text(self.cursor, TextStyle::Label, line);
|
||||
self.cursor.y += 16.0;
|
||||
}
|
||||
self.cursor.y += 16.0; // Padding
|
||||
self.cursor.y += self.layout_options.item_spacing.y;
|
||||
}
|
||||
|
||||
/// A radio button
|
||||
|
@ -88,7 +129,10 @@ impl Gui {
|
|||
let id = self.get_id(&label);
|
||||
let rect = Rect {
|
||||
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);
|
||||
|
@ -100,7 +144,7 @@ impl Gui {
|
|||
text: label,
|
||||
});
|
||||
|
||||
self.cursor.y += rect.size.y + 16.0;
|
||||
self.cursor.y += rect.size.y + self.layout_options.item_spacing.y;
|
||||
interact
|
||||
}
|
||||
|
||||
|
@ -115,7 +159,10 @@ impl Gui {
|
|||
let id = self.get_id(&label);
|
||||
let rect = Rect {
|
||||
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);
|
||||
|
||||
|
@ -134,7 +181,7 @@ impl Gui {
|
|||
value: *value,
|
||||
});
|
||||
|
||||
self.cursor.y += rect.size.y + 16.0;
|
||||
self.cursor.y += rect.size.y + self.layout_options.item_spacing.y;
|
||||
|
||||
interact
|
||||
}
|
29
src/lib.rs
29
src/lib.rs
|
@ -13,16 +13,16 @@ use std::sync::Mutex;
|
|||
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
use crate::{math::Vec2, types::*};
|
||||
use crate::types::*;
|
||||
|
||||
pub mod app;
|
||||
pub mod gui;
|
||||
pub mod emgui;
|
||||
pub mod layout;
|
||||
pub mod math;
|
||||
pub mod style;
|
||||
pub mod types;
|
||||
|
||||
/*
|
||||
|
||||
// Fast compilation, slow code:
|
||||
fn foo(x: &dyn Trait);
|
||||
|
||||
|
@ -43,26 +43,15 @@ pub fn show_gui(raw_input_json: &str) -> String {
|
|||
|
||||
lazy_static::lazy_static! {
|
||||
static ref APP: Mutex<app::App> = Default::default();
|
||||
static ref LAST_INPUT: Mutex<RawInput> = Default::default();
|
||||
static ref GUI_STATE: Mutex<gui::GuiState> = Default::default();
|
||||
static ref EMGUI: Mutex<crate::emgui::Emgui> = Default::default();
|
||||
}
|
||||
|
||||
let gui_input = GuiInput::from_last_and_new(&LAST_INPUT.lock().unwrap(), &raw_input);
|
||||
*LAST_INPUT.lock().unwrap() = raw_input;
|
||||
let mut emgui = EMGUI.lock().unwrap();
|
||||
emgui.new_frame(raw_input);
|
||||
|
||||
let mut gui = gui::Gui {
|
||||
commands: Vec::new(),
|
||||
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);
|
||||
use crate::app::GuiSettings;
|
||||
APP.lock().unwrap().show_gui(&mut emgui.layout);
|
||||
|
||||
*GUI_STATE.lock().unwrap() = gui.state;
|
||||
|
||||
let commands = style::into_paint_commands(gui.gui_commands());
|
||||
let commands = emgui.paint();
|
||||
serde_json::to_string(&commands).unwrap()
|
||||
}
|
||||
|
|
|
@ -45,6 +45,10 @@ pub struct 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 {
|
||||
Rect {
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -86,5 +90,5 @@ pub fn remap_clamp(from: f32, from_min: f32, from_max: f32, to_min: f32, to_max:
|
|||
} else {
|
||||
(from - from_min) / (from_max - from_min)
|
||||
};
|
||||
lerp(t, to_min, to_max)
|
||||
lerp(to_min, to_max, t)
|
||||
}
|
||||
|
|
42
src/style.rs
42
src/style.rs
|
@ -1,7 +1,19 @@
|
|||
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
|
||||
fn translate_cmd(out_commands: &mut Vec<PaintCmd>, cmd: GuiCmd) {
|
||||
fn translate_cmd(out_commands: &mut Vec<PaintCmd>, style: &Style, cmd: GuiCmd) {
|
||||
match cmd {
|
||||
GuiCmd::PaintCommands(mut commands) => out_commands.append(&mut commands),
|
||||
GuiCmd::Button {
|
||||
|
@ -29,7 +41,7 @@ fn translate_cmd(out_commands: &mut Vec<PaintCmd>, cmd: GuiCmd) {
|
|||
font: "14px Palatino".to_string(),
|
||||
pos: Vec2 {
|
||||
x: rect.center().x,
|
||||
y: rect.center().y + 14.0 / 2.0,
|
||||
y: rect.center().y + 6.0,
|
||||
},
|
||||
text,
|
||||
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),
|
||||
],
|
||||
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(),
|
||||
pos: Vec2 {
|
||||
x: box_rect.max().x + 4.0,
|
||||
y: rect.center().y + 14.0 / 2.0,
|
||||
y: rect.center().y + 5.0,
|
||||
},
|
||||
text,
|
||||
text_align: TextAlign::Start,
|
||||
|
@ -153,12 +165,17 @@ fn translate_cmd(out_commands: &mut Vec<PaintCmd>, cmd: GuiCmd) {
|
|||
rect,
|
||||
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_rect =
|
||||
Rect::from_center_size(vec2(marker_center_x, rect.center().y), vec2(16.0, 16.0));
|
||||
let marker_rect = Rect::from_center_size(
|
||||
vec2(marker_center_x, thin_rect.center().y),
|
||||
vec2(16.0, 16.0),
|
||||
);
|
||||
|
||||
let marker_fill_style = if interact.active {
|
||||
"#888888ff".to_string()
|
||||
|
@ -187,7 +204,10 @@ fn translate_cmd(out_commands: &mut Vec<PaintCmd>, cmd: GuiCmd) {
|
|||
out_commands.push(PaintCmd::Text {
|
||||
fill_style: "#ffffffbb".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_align: TextAlign::Start,
|
||||
});
|
||||
|
@ -204,7 +224,7 @@ fn translate_cmd(out_commands: &mut Vec<PaintCmd>, cmd: GuiCmd) {
|
|||
out_commands.push(PaintCmd::Text {
|
||||
fill_style,
|
||||
font: "14px Palatino".to_string(),
|
||||
pos,
|
||||
pos: pos + vec2(0.0, 7.0), // TODO: FIXME
|
||||
text,
|
||||
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![];
|
||||
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
|
||||
}
|
||||
|
|
24
src/types.rs
24
src/types.rs
|
@ -16,7 +16,7 @@ pub struct RawInput {
|
|||
}
|
||||
|
||||
/// What the gui maintains
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct GuiInput {
|
||||
/// Is the button currently down?
|
||||
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)]
|
||||
pub struct InteractInfo {
|
||||
pub hovered: bool,
|
||||
|
|
Loading…
Reference in a new issue