persitent interaction with buttons

This commit is contained in:
Emil Ernerfeldt 2018-12-26 15:28:38 +01:00
parent 094f8216c5
commit 7c3aa61c02
6 changed files with 105 additions and 53 deletions

View file

@ -3,3 +3,6 @@ set -eu
echo "Lint and clean up typescript:" echo "Lint and clean up typescript:"
tslint --fix docs/*.ts tslint --fix docs/*.ts
echo "Cargo clippy"
cargo clippy

View file

@ -1,14 +1,11 @@
use crate::gui::Gui; use crate::gui::Gui;
#[derive(Default)]
pub struct App { pub struct App {
count: i32, count: i32,
} }
impl App { impl App {
pub fn new() -> Self {
App { count: 0 }
}
pub fn show_gui(&mut self, gui: &mut Gui) { pub fn show_gui(&mut self, gui: &mut Gui) {
if gui.button("Click me").clicked { if gui.button("Click me").clicked {
self.count += 1; self.count += 1;
@ -22,7 +19,7 @@ impl App {
self.count self.count
)); ));
let commands_json = serde_json::to_string_pretty(&gui.paint_commands()).unwrap(); let commands_json = format!("{:#?}", gui.gui_commands());
gui.label(format!("All paint commands:\n{}", commands_json)); gui.label(format!("All gui commands: {}", commands_json));
} }
} }

View file

@ -3,63 +3,58 @@ use crate::types::*;
// TODO: implement Gui on this so we can add children to a widget // TODO: implement Gui on this so we can add children to a widget
// pub struct Widget {} // pub struct Widget {}
pub struct Gui { type Id = u64;
commands: Vec<GuiCmd>,
input: GuiInput,
cursor: Vec2, #[derive(Clone, Copy, Debug, Default)]
pub struct GuiState {
/// The widget being interacted with (e.g. dragged, in case of a slider).
pub active_id: Option<Id>,
}
pub struct Gui {
pub commands: Vec<GuiCmd>,
pub cursor: Vec2,
pub input: GuiInput,
pub state: GuiState,
} }
impl Gui { impl Gui {
pub fn new(input: GuiInput) -> Self {
Gui {
commands: vec![],
input,
cursor: Vec2 { x: 32.0, y: 32.0 },
}
}
pub fn input(&self) -> &GuiInput { pub fn input(&self) -> &GuiInput {
&self.input &self.input
} }
pub fn into_commands(self) -> Vec<GuiCmd> { pub fn gui_commands(&self) -> &[GuiCmd] {
self.commands
}
pub fn paint_commands(&self) -> &[GuiCmd] {
&self.commands &self.commands
} }
fn rect(&mut self, rect: Rect, style: RectStyle) -> InteractInfo {
let hovered = rect.contains(self.input.mouse_pos);
let clicked = hovered && self.input.mouse_clicked;
let interact = InteractInfo { hovered, clicked };
self.commands.push(GuiCmd::Rect {
interact,
rect,
style,
});
interact
}
fn text<S: Into<String>>(&mut self, pos: Vec2, style: TextStyle, text: S) {
self.commands.push(GuiCmd::Text {
pos,
style,
text: text.into(),
text_align: TextAlign::Start,
});
}
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
pub fn button<S: Into<String>>(&mut self, text: S) -> InteractInfo { pub fn button<S: Into<String>>(&mut self, text: S) -> InteractInfo {
let text: String = text.into();
let id = self.get_id(&text);
let rect = Rect { let rect = Rect {
pos: self.cursor, pos: self.cursor,
size: Vec2 { x: 200.0, y: 32.0 }, // TODO: get from some settings size: Vec2 { x: 200.0, y: 32.0 }, // TODO: get from some settings
}; };
let interact = self.rect(rect, RectStyle::Button);
let hovered = rect.contains(self.input.mouse_pos);
let clicked = hovered && self.input.mouse_clicked;
if clicked {
self.state.active_id = Some(id);
}
let active = self.state.active_id == Some(id);
let interact = InteractInfo {
hovered,
clicked,
active,
};
self.commands.push(GuiCmd::Rect {
interact,
rect,
style: RectStyle::Button,
});
// TODO: clip-rect of text // TODO: clip-rect of text
self.text( self.text(
@ -75,7 +70,8 @@ impl Gui {
} }
pub fn label<S: Into<String>>(&mut self, text: S) { pub fn label<S: Into<String>>(&mut self, text: S) {
for line in text.into().split("\n") { let text: String = text.into();
for line in text.split('\n') {
self.text(self.cursor, TextStyle::Label, line); self.text(self.cursor, TextStyle::Label, line);
self.cursor.y += 16.0; self.cursor.y += 16.0;
} }
@ -83,4 +79,20 @@ impl Gui {
} }
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
fn get_id(&self, id_str: &str) -> Id {
use std::hash::Hasher;
let mut hasher = std::collections::hash_map::DefaultHasher::new();
hasher.write(id_str.as_bytes());
hasher.finish()
}
fn text<S: Into<String>>(&mut self, pos: Vec2, style: TextStyle, text: S) {
self.commands.push(GuiCmd::Text {
pos,
style,
text: text.into(),
text_align: TextAlign::Start,
});
}
} }

View file

@ -39,16 +39,27 @@ pub fn show_gui(raw_input_json: &str) -> String {
let raw_input: RawInput = serde_json::from_str(raw_input_json).unwrap(); let raw_input: RawInput = serde_json::from_str(raw_input_json).unwrap();
lazy_static::lazy_static! { lazy_static::lazy_static! {
static ref APP: Mutex<app::App> = Mutex::new(app::App::new()); static ref APP: Mutex<app::App> = Default::default();
static ref LAST_INPUT: Mutex<RawInput> = Default::default(); static ref LAST_INPUT: Mutex<RawInput> = 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 gui_input = GuiInput::from_last_and_new(&LAST_INPUT.lock().unwrap(), &raw_input);
*LAST_INPUT.lock().unwrap() = raw_input; *LAST_INPUT.lock().unwrap() = raw_input;
let mut gui = gui::Gui::new(gui_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); APP.lock().unwrap().show_gui(&mut gui);
let commands = gui.into_commands();
let commands = style::into_paint_commands(commands); *GUI_STATE.lock().unwrap() = gui.state;
let commands = style::into_paint_commands(gui.gui_commands());
serde_json::to_string(&commands).unwrap() serde_json::to_string(&commands).unwrap()
} }

View file

@ -9,7 +9,9 @@ fn translate_cmd(cmd: GuiCmd) -> PaintCmd {
interact, interact,
} => match style { } => match style {
RectStyle::Button => { RectStyle::Button => {
let fill_style = if interact.hovered { let fill_style = if interact.active {
"#888888ff".to_string()
} else if interact.hovered {
"#444444ff".to_string() "#444444ff".to_string()
} else { } else {
"#222222ff".to_string() "#222222ff".to_string()
@ -43,6 +45,6 @@ fn translate_cmd(cmd: GuiCmd) -> PaintCmd {
} }
} }
pub fn into_paint_commands(gui_commands: Vec<GuiCmd>) -> Vec<PaintCmd> { pub fn into_paint_commands(gui_commands: &[GuiCmd]) -> Vec<PaintCmd> {
gui_commands.into_iter().map(translate_cmd).collect() gui_commands.iter().cloned().map(translate_cmd).collect()
} }

View file

@ -74,10 +74,37 @@ 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,
/// The mouse went got pressed on this thing this frame
pub clicked: bool, pub clicked: bool,
/// The mouse is interacting with this thing (e.g. dragging it)
pub active: bool,
} }
#[derive(Clone, Copy, Debug, Serialize)] #[derive(Clone, Copy, Debug, Serialize)]