diff --git a/emigui/src/emigui.rs b/emigui/src/emigui.rs index ad49da36..131944d4 100644 --- a/emigui/src/emigui.rs +++ b/emigui/src/emigui.rs @@ -6,7 +6,7 @@ use crate::{ math::{clamp, remap_clamp, vec2}, mesher::{Mesher, Vertex}, style::Style, - types::{Color, GuiCmd, GuiInput, PaintCmd}, + types::{Color, GuiInput, PaintCmd}, widgets::*, FontSizes, Fonts, Mesh, RawInput, Texture, }; @@ -63,7 +63,7 @@ fn show_font_texture(texture: &Texture, gui: &mut Region) { }; let mut mesh = Mesh::default(); mesh.add_rect(top_left, bottom_right); - gui.add_graphic(GuiCmd::PaintCommands(vec![PaintCmd::Mesh(mesh)])); + gui.add_paint_cmd(PaintCmd::Mesh(mesh)); if let Some(mouse_pos) = gui.input().mouse_pos { if interact.hovered { @@ -102,7 +102,7 @@ fn show_font_texture(texture: &Texture, gui: &mut Region) { }; let mut mesh = Mesh::default(); mesh.add_rect(top_left, bottom_right); - gui.add_graphic(GuiCmd::PaintCommands(vec![PaintCmd::Mesh(mesh)])); + gui.add_paint_cmd(PaintCmd::Mesh(mesh)); }); } } @@ -153,9 +153,7 @@ impl Emigui { } pub fn paint(&mut self) -> Mesh { - let gui_commands = self.data.graphics.lock().unwrap().drain(); - let paint_commands = crate::style::into_paint_commands(gui_commands, &self.data.style()); - + let paint_commands: Vec = self.data.graphics.lock().unwrap().drain().collect(); let mut mesher = Mesher::new(self.last_input.pixels_per_point); mesher.paint(&self.data.fonts, &paint_commands); let mesh = mesher.mesh; diff --git a/emigui/src/layout.rs b/emigui/src/layout.rs index 6369a381..0d9c75de 100644 --- a/emigui/src/layout.rs +++ b/emigui/src/layout.rs @@ -116,12 +116,12 @@ pub fn make_id(source: &H) -> Id { /// TODO: improve this #[derive(Clone, Default)] pub struct GraphicLayers { - pub(crate) graphics: Vec, - pub(crate) hovering_graphics: Vec, + pub(crate) graphics: Vec, + pub(crate) hovering_graphics: Vec, } impl GraphicLayers { - pub fn drain(&mut self) -> impl ExactSizeIterator { + pub fn drain(&mut self) -> impl ExactSizeIterator { // TODO: there must be a nicer way to do this? let mut all_commands: Vec<_> = self.graphics.drain(..).collect(); all_commands.extend(self.hovering_graphics.drain(..)); @@ -223,7 +223,15 @@ where let mut graphics = data.graphics.lock().unwrap(); let popup_graphics = graphics.graphics.split_off(num_graphics_before); - graphics.hovering_graphics.push(GuiCmd::Window { rect }); + graphics.hovering_graphics.push(PaintCmd::Rect { + corner_radius: 5.0, + fill_color: Some(style.background_fill_color()), + outline: Some(Outline { + color: gray(255, 255), // TODO + width: 1.0, + }), + rect, + }); graphics.hovering_graphics.extend(popup_graphics); } @@ -261,12 +269,17 @@ impl Region { /// It is up to the caller to make sure there is room for this. /// Can be used for free painting. /// NOTE: all coordinates are screen coordinates! - pub fn add_graphic(&mut self, gui_cmd: GuiCmd) { - self.data.graphics.lock().unwrap().graphics.push(gui_cmd) + pub fn add_paint_cmd(&mut self, paint_cmd: PaintCmd) { + self.data.graphics.lock().unwrap().graphics.push(paint_cmd) } - pub fn add_paint_cmd(&mut self, paint_cmd: PaintCmd) { - self.add_graphic(GuiCmd::PaintCommands(vec![paint_cmd])) + pub fn add_paint_cmds(&mut self, mut cmds: Vec) { + self.data + .graphics + .lock() + .unwrap() + .graphics + .append(&mut cmds) } /// Options for this region, and any child regions we may spawn. @@ -315,11 +328,11 @@ impl Region { let text_style = TextStyle::Button; let font = &self.fonts()[text_style]; let (text, text_size) = font.layout_multiline(&text, self.width()); - let text_cursor = self.cursor + self.style().button_padding; + let text_cursor = self.cursor + self.style.button_padding; let interact = self.reserve_space( vec2( self.available_space.x, - text_size.y + 2.0 * self.style().button_padding.y, + text_size.y + 2.0 * self.style.button_padding.y, ), Some(id), ); @@ -336,9 +349,40 @@ impl Region { memory.open_foldables.contains(&id) }; - self.add_graphic(GuiCmd::FoldableHeader { interact, open }); + let fill_color = self.style.interact_fill_color(&interact); + let stroke_color = self.style.interact_stroke_color(&interact); + + self.add_paint_cmd(PaintCmd::Rect { + corner_radius: 3.0, + fill_color: Some(fill_color), + outline: None, + rect: interact.rect, + }); + + let (small_icon_rect, _) = self.style.icon_rectangles(&interact.rect); + // Draw a minus: + self.add_paint_cmd(PaintCmd::Line { + points: vec![ + vec2(small_icon_rect.min().x, small_icon_rect.center().y), + vec2(small_icon_rect.max().x, small_icon_rect.center().y), + ], + color: stroke_color, + width: self.style.line_width, + }); + if !open { + // Draw it as a plus: + self.add_paint_cmd(PaintCmd::Line { + points: vec![ + vec2(small_icon_rect.center().x, small_icon_rect.min().y), + vec2(small_icon_rect.center().x, small_icon_rect.max().y), + ], + color: stroke_color, + width: self.style.line_width, + }); + } + self.add_text( - text_cursor + vec2(self.style().start_icon_width, 0.0), + text_cursor + vec2(self.style.start_icon_width, 0.0), text_style, text, None, @@ -359,7 +403,7 @@ impl Region { where F: FnOnce(&mut Region), { - let indent = vec2(self.style().indent, 0.0); + let indent = vec2(self.style.indent, 0.0); let mut child_region = Region { data: self.data.clone(), style: self.style, @@ -435,7 +479,7 @@ impl Region { F: FnOnce(&mut [Region]) -> R, { // TODO: ensure there is space - let padding = self.style().item_spacing.x; + let padding = self.style.item_spacing.x; let total_padding = padding * (num_columns as f32 - 1.0); let column_width = (self.available_space.x - total_padding) / (num_columns as f32); @@ -473,7 +517,7 @@ impl Region { // ------------------------------------------------------------------------ pub fn reserve_space(&mut self, size: Vec2, interaction_id: Option) -> InteractInfo { - let pos = self.reserve_space_without_padding(size + self.style().item_spacing); + let pos = self.reserve_space_without_padding(size + self.style.item_spacing); let rect = Rect::from_min_size(pos, size); let mut memory = self.data.memory.lock().unwrap(); @@ -580,8 +624,9 @@ impl Region { text: Vec, color: Option, ) { + let color = color.unwrap_or_else(|| self.style().text_color()); for fragment in text { - self.add_graphic(GuiCmd::Text { + self.add_paint_cmd(PaintCmd::Text { color, pos: pos + vec2(0.0, fragment.y_offset), text: fragment.text, diff --git a/emigui/src/style.rs b/emigui/src/style.rs index aadf3f03..ff2f43bd 100644 --- a/emigui/src/style.rs +++ b/emigui/src/style.rs @@ -43,16 +43,16 @@ impl Default for Style { impl Style { /// e.g. the background of the slider - fn background_fill_color(&self) -> Color { + pub fn background_fill_color(&self) -> Color { gray(34, 200) } - fn text_color(&self) -> Color { + pub fn text_color(&self) -> Color { gray(255, 200) } /// Fill color of the interactive part of a component (button, slider grab, checkbox, ...) - fn interact_fill_color(&self, interact: &InteractInfo) -> Color { + pub fn interact_fill_color(&self, interact: &InteractInfo) -> Color { if interact.active { srgba(100, 100, 200, 255) } else if interact.hovered { @@ -63,7 +63,7 @@ impl Style { } /// Stroke and text color of the interactive part of a component (button, slider grab, checkbox, ...) - fn interact_stroke_color(&self, interact: &InteractInfo) -> Color { + pub fn interact_stroke_color(&self, interact: &InteractInfo) -> Color { if interact.active { gray(255, 255) } else if interact.hovered { @@ -74,7 +74,7 @@ impl Style { } /// Returns small icon rectangle and big icon rectangle - fn icon_rectangles(&self, rect: &Rect) -> (Rect, Rect) { + pub fn icon_rectangles(&self, rect: &Rect) -> (Rect, Rect) { let box_side = 16.0; let big_icon_rect = Rect::from_center_size( vec2(rect.min().x + 4.0 + box_side * 0.5, rect.center().y), @@ -86,170 +86,3 @@ impl Style { (small_icon_rect, big_icon_rect) } } - -// ---------------------------------------------------------------------------- - -fn translate_cmd(out_commands: &mut Vec, style: &Style, cmd: GuiCmd) { - match cmd { - GuiCmd::PaintCommands(mut commands) => out_commands.append(&mut commands), - GuiCmd::Button { interact } => { - out_commands.push(PaintCmd::Rect { - corner_radius: 10.0, - fill_color: Some(style.interact_fill_color(&interact)), - outline: None, - rect: interact.rect, - }); - } - GuiCmd::Checkbox { checked, interact } => { - let (small_icon_rect, big_icon_rect) = style.icon_rectangles(&interact.rect); - out_commands.push(PaintCmd::Rect { - corner_radius: 3.0, - fill_color: Some(style.interact_fill_color(&interact)), - outline: None, - rect: big_icon_rect, - }); - - let stroke_color = style.interact_stroke_color(&interact); - - if checked { - out_commands.push(PaintCmd::Line { - points: vec![ - vec2(small_icon_rect.min().x, small_icon_rect.center().y), - vec2(small_icon_rect.center().x, small_icon_rect.max().y), - vec2(small_icon_rect.max().x, small_icon_rect.min().y), - ], - color: stroke_color, - width: style.line_width, - }); - } - } - GuiCmd::FoldableHeader { interact, open } => { - let fill_color = style.interact_fill_color(&interact); - let stroke_color = style.interact_stroke_color(&interact); - - out_commands.push(PaintCmd::Rect { - corner_radius: 3.0, - fill_color: Some(fill_color), - outline: None, - rect: interact.rect, - }); - - let (small_icon_rect, _) = style.icon_rectangles(&interact.rect); - // Draw a minus: - out_commands.push(PaintCmd::Line { - points: vec![ - vec2(small_icon_rect.min().x, small_icon_rect.center().y), - vec2(small_icon_rect.max().x, small_icon_rect.center().y), - ], - color: stroke_color, - width: style.line_width, - }); - if !open { - // Draw it as a plus: - out_commands.push(PaintCmd::Line { - points: vec![ - vec2(small_icon_rect.center().x, small_icon_rect.min().y), - vec2(small_icon_rect.center().x, small_icon_rect.max().y), - ], - color: stroke_color, - width: style.line_width, - }); - } - } - GuiCmd::RadioButton { checked, interact } => { - let fill_color = style.interact_fill_color(&interact); - let stroke_color = style.interact_stroke_color(&interact); - - let (small_icon_rect, big_icon_rect) = style.icon_rectangles(&interact.rect); - - out_commands.push(PaintCmd::Circle { - center: big_icon_rect.center(), - fill_color: Some(fill_color), - outline: None, - radius: big_icon_rect.size.x / 2.0, - }); - - if checked { - out_commands.push(PaintCmd::Circle { - center: small_icon_rect.center(), - fill_color: Some(stroke_color), - outline: None, - radius: small_icon_rect.size.x / 2.0, - }); - } - } - GuiCmd::Slider { - interact, - max, - min, - value, - } => { - let rect = interact.rect; - let thickness = rect.size().y; - let thin_size = vec2(rect.size.x, thickness / 5.0); - let thin_rect = Rect::from_center_size(rect.center(), thin_size); - let marker_center_x = remap_clamp(value, min, max, rect.min().x, rect.max().x); - - out_commands.push(PaintCmd::Rect { - corner_radius: 4.0, - fill_color: Some(style.background_fill_color()), - outline: Some(Outline { - color: gray(200, 255), // TODO - width: 1.0, - }), - rect: thin_rect, - }); - - out_commands.push(PaintCmd::Circle { - center: vec2(marker_center_x, thin_rect.center().y), - fill_color: Some(style.interact_fill_color(&interact)), - outline: Some(Outline { - color: style.interact_stroke_color(&interact), - width: 1.5, - }), - radius: thickness / 3.0, - }); - } - GuiCmd::Text { - color, - pos, - text, - text_style, - x_offsets, - } => { - let color = color.unwrap_or_else(|| style.text_color()); - out_commands.push(PaintCmd::Text { - color, - text_style, - pos, - text, - x_offsets, - }); - } - GuiCmd::Window { rect } => { - out_commands.push(PaintCmd::Rect { - corner_radius: 5.0, - fill_color: Some(style.background_fill_color()), - outline: Some(Outline { - color: gray(255, 255), // TODO - width: 1.0, - }), - rect, - }); - } - } -} - -pub fn into_paint_commands( - gui_commands: GuiCmdIterator, - style: &Style, -) -> Vec -where - GuiCmdIterator: Iterator, -{ - let mut paint_commands = vec![]; - for gui_cmd in gui_commands { - translate_cmd(&mut paint_commands, style, gui_cmd) - } - paint_commands -} diff --git a/emigui/src/types.rs b/emigui/src/types.rs index 1dffdfad..7e601196 100644 --- a/emigui/src/types.rs +++ b/emigui/src/types.rs @@ -111,47 +111,6 @@ pub struct InteractInfo { pub rect: Rect, } -#[derive(Clone, Debug, Serialize)] -pub enum GuiCmd { - PaintCommands(Vec), - /// The background for a button - Button { - interact: InteractInfo, - }, - Checkbox { - checked: bool, - interact: InteractInfo, - }, - /// The header button background for a foldable region - FoldableHeader { - interact: InteractInfo, - open: bool, - }, - RadioButton { - checked: bool, - interact: InteractInfo, - }, - Slider { - interact: InteractInfo, - max: f32, - min: f32, - value: f32, - }, - /// A string of text with a position for each character. - Text { - color: Option, - pos: Vec2, - text: String, - text_style: TextStyle, - /// Start each character in the text, as offset from pos. - x_offsets: Vec, - }, - /// Background of e.g. a popup - Window { - rect: Rect, - }, -} - // ---------------------------------------------------------------------------- #[derive(Clone, Debug, Serialize)] diff --git a/emigui/src/widgets.rs b/emigui/src/widgets.rs index f7cc0e62..449cff51 100644 --- a/emigui/src/widgets.rs +++ b/emigui/src/widgets.rs @@ -3,8 +3,8 @@ use crate::{ fonts::TextStyle, layout::{make_id, Align, Direction, GuiResponse, Id, Region}, - math::{remap_clamp, vec2, Vec2}, - types::{Color, GuiCmd, PaintCmd}, + math::{remap_clamp, vec2, Rect, Vec2}, + types::{gray, Color, Outline, PaintCmd}, }; // ---------------------------------------------------------------------------- @@ -91,7 +91,12 @@ impl Widget for Button { size.y = size.y.max(region.style().clickable_diameter); let interact = region.reserve_space(size, Some(id)); let text_cursor = interact.rect.left_center() + vec2(padding.x, -0.5 * text_size.y); - region.add_graphic(GuiCmd::Button { interact }); + region.add_paint_cmd(PaintCmd::Rect { + corner_radius: 10.0, + fill_color: Some(region.style().interact_fill_color(&interact)), + outline: None, + rect: interact.rect, + }); region.add_text(text_cursor, text_style, text, self.text_color); region.response(interact) } @@ -140,10 +145,28 @@ impl<'a> Widget for Checkbox<'a> { if interact.clicked { *self.checked = !*self.checked; } - region.add_graphic(GuiCmd::Checkbox { - checked: *self.checked, - interact, + let (small_icon_rect, big_icon_rect) = region.style().icon_rectangles(&interact.rect); + region.add_paint_cmd(PaintCmd::Rect { + corner_radius: 3.0, + fill_color: Some(region.style().interact_fill_color(&interact)), + outline: None, + rect: big_icon_rect, }); + + let stroke_color = region.style().interact_stroke_color(&interact); + + if *self.checked { + region.add_paint_cmd(PaintCmd::Line { + points: vec![ + vec2(small_icon_rect.min().x, small_icon_rect.center().y), + vec2(small_icon_rect.center().x, small_icon_rect.max().y), + vec2(small_icon_rect.max().x, small_icon_rect.min().y), + ], + color: stroke_color, + width: region.style().line_width, + }); + } + region.add_text(text_cursor, text_style, text, self.text_color); region.response(interact) } @@ -193,10 +216,28 @@ impl Widget for RadioButton { let text_cursor = interact.rect.min() + region.style().button_padding + vec2(region.style().start_icon_width, 0.0); - region.add_graphic(GuiCmd::RadioButton { - checked: self.checked, - interact, + + let fill_color = region.style().interact_fill_color(&interact); + let stroke_color = region.style().interact_stroke_color(&interact); + + let (small_icon_rect, big_icon_rect) = region.style().icon_rectangles(&interact.rect); + + region.add_paint_cmd(PaintCmd::Circle { + center: big_icon_rect.center(), + fill_color: Some(fill_color), + outline: None, + radius: big_icon_rect.size.x / 2.0, }); + + if self.checked { + region.add_paint_cmd(PaintCmd::Circle { + center: small_icon_rect.center(), + fill_color: Some(stroke_color), + outline: None, + radius: small_icon_rect.size.x / 2.0, + }); + } + region.add_text(text_cursor, text_style, text, self.text_color); region.response(interact) } @@ -349,12 +390,36 @@ impl<'a> Widget for Slider<'a> { } } - region.add_graphic(GuiCmd::Slider { - interact, - max, - min, - value: (self.get_set_value)(None), - }); + // Paint it: + { + let value = (self.get_set_value)(None); + + let rect = interact.rect; + let thickness = rect.size().y; + let thin_size = vec2(rect.size.x, thickness / 5.0); + let thin_rect = Rect::from_center_size(rect.center(), thin_size); + let marker_center_x = remap_clamp(value, min, max, rect.min().x, rect.max().x); + + region.add_paint_cmd(PaintCmd::Rect { + corner_radius: 4.0, + fill_color: Some(region.style().background_fill_color()), + outline: Some(Outline { + color: gray(200, 255), // TODO + width: 1.0, + }), + rect: thin_rect, + }); + + region.add_paint_cmd(PaintCmd::Circle { + center: vec2(marker_center_x, thin_rect.center().y), + fill_color: Some(region.style().interact_fill_color(&interact)), + outline: Some(Outline { + color: region.style().interact_stroke_color(&interact), + width: 1.5, + }), + radius: thickness / 3.0, + }); + } region.response(interact) } @@ -412,12 +477,11 @@ impl Widget for Separator { ) } }; - let paint_cmd = PaintCmd::Line { + region.add_paint_cmd(PaintCmd::Line { points, color: Color::WHITE, width: self.line_width, - }; - region.add_graphic(GuiCmd::PaintCommands(vec![paint_cmd])); + }); region.response(interact) } } diff --git a/example/src/app.rs b/example/src/app.rs index 331fb2ed..4a94db04 100644 --- a/example/src/app.rs +++ b/example/src/app.rs @@ -129,7 +129,7 @@ impl App { }), }); } - gui.add_graphic(GuiCmd::PaintCommands(cmds)); + gui.add_paint_cmds(cmds); }); gui.foldable("Slider example", |gui| {