Refactor: Merge LayoutOptions and Style

This commit is contained in:
Emil Ernerfeldt 2019-03-11 15:39:54 +01:00
parent 7cbf8e45bc
commit 87288634e9
5 changed files with 92 additions and 140 deletions

View file

@ -2,10 +2,10 @@ use std::sync::Arc;
use crate::{ use crate::{
label, layout, label, layout,
layout::{show_popup, LayoutOptions, Region}, layout::{show_popup, Region},
math::{clamp, remap_clamp, vec2}, math::{clamp, remap_clamp, vec2},
mesher::{Mesher, Vertex}, mesher::{Mesher, Vertex},
style, style::Style,
types::{Color, GuiCmd, GuiInput, PaintCmd}, types::{Color, GuiCmd, GuiInput, PaintCmd},
widgets::*, widgets::*,
FontSizes, Fonts, Mesh, RawInput, Texture, FontSizes, Fonts, Mesh, RawInput, Texture,
@ -17,26 +17,19 @@ struct Stats {
num_triangles: usize, num_triangles: usize,
} }
fn show_options(options: &mut LayoutOptions, gui: &mut Region) { fn show_style(style: &mut Style, gui: &mut Region) {
if gui.add(Button::new("Reset LayoutOptions")).clicked { if gui.add(Button::new("Reset style")).clicked {
*options = Default::default();
}
gui.add(Slider::f32(&mut options.item_spacing.x, 0.0, 10.0).text("item_spacing.x"));
gui.add(Slider::f32(&mut options.item_spacing.y, 0.0, 10.0).text("item_spacing.y"));
gui.add(Slider::f32(&mut options.window_padding.x, 0.0, 10.0).text("window_padding.x"));
gui.add(Slider::f32(&mut options.window_padding.y, 0.0, 10.0).text("window_padding.y"));
gui.add(Slider::f32(&mut options.indent, 0.0, 100.0).text("indent"));
gui.add(Slider::f32(&mut options.button_padding.x, 0.0, 20.0).text("button_padding.x"));
gui.add(Slider::f32(&mut options.button_padding.y, 0.0, 20.0).text("button_padding.y"));
gui.add(Slider::f32(&mut options.clickable_diameter, 0.0, 60.0).text("clickable_diameter"));
gui.add(Slider::f32(&mut options.start_icon_width, 0.0, 60.0).text("start_icon_width"));
}
fn show_style(style: &mut style::Style, gui: &mut Region) {
if gui.add(Button::new("Reset Style")).clicked {
*style = Default::default(); *style = Default::default();
} }
gui.add(Checkbox::new(&mut style.debug_rects, "debug_rects")); gui.add(Slider::f32(&mut style.item_spacing.x, 0.0, 10.0).text("item_spacing.x"));
gui.add(Slider::f32(&mut style.item_spacing.y, 0.0, 10.0).text("item_spacing.y"));
gui.add(Slider::f32(&mut style.window_padding.x, 0.0, 10.0).text("window_padding.x"));
gui.add(Slider::f32(&mut style.window_padding.y, 0.0, 10.0).text("window_padding.y"));
gui.add(Slider::f32(&mut style.indent, 0.0, 100.0).text("indent"));
gui.add(Slider::f32(&mut style.button_padding.x, 0.0, 20.0).text("button_padding.x"));
gui.add(Slider::f32(&mut style.button_padding.y, 0.0, 20.0).text("button_padding.y"));
gui.add(Slider::f32(&mut style.clickable_diameter, 0.0, 60.0).text("clickable_diameter"));
gui.add(Slider::f32(&mut style.start_icon_width, 0.0, 60.0).text("start_icon_width"));
gui.add(Slider::f32(&mut style.line_width, 0.0, 10.0).text("line_width")); gui.add(Slider::f32(&mut style.line_width, 0.0, 10.0).text("line_width"));
} }
@ -119,7 +112,6 @@ fn show_font_texture(texture: &Texture, gui: &mut Region) {
pub struct Emigui { pub struct Emigui {
pub last_input: RawInput, pub last_input: RawInput,
pub data: Arc<layout::Data>, pub data: Arc<layout::Data>,
pub style: style::Style,
stats: Stats, stats: Stats,
} }
@ -128,7 +120,6 @@ impl Emigui {
Emigui { Emigui {
last_input: Default::default(), last_input: Default::default(),
data: Arc::new(layout::Data::new(pixels_per_point)), data: Arc::new(layout::Data::new(pixels_per_point)),
style: Default::default(),
stats: Default::default(), stats: Default::default(),
} }
} }
@ -151,7 +142,7 @@ impl Emigui {
let size = self.data.input.screen_size; let size = self.data.input.screen_size;
layout::Region { layout::Region {
data: self.data.clone(), data: self.data.clone(),
options: self.data.options(), style: self.data.style(),
id: Default::default(), id: Default::default(),
dir: layout::Direction::Vertical, dir: layout::Direction::Vertical,
align: layout::Align::Center, align: layout::Align::Center,
@ -163,7 +154,7 @@ impl Emigui {
pub fn paint(&mut self) -> Mesh { pub fn paint(&mut self) -> Mesh {
let gui_commands = self.data.graphics.lock().unwrap().drain(); let gui_commands = self.data.graphics.lock().unwrap().drain();
let paint_commands = style::into_paint_commands(gui_commands, &self.style); let paint_commands = crate::style::into_paint_commands(gui_commands, &self.data.style());
let mut mesher = Mesher::new(self.last_input.pixels_per_point); let mut mesher = Mesher::new(self.last_input.pixels_per_point);
mesher.paint(&self.data.fonts, &paint_commands); mesher.paint(&self.data.fonts, &paint_commands);
@ -174,14 +165,10 @@ impl Emigui {
} }
pub fn example(&mut self, region: &mut Region) { pub fn example(&mut self, region: &mut Region) {
region.foldable("LayoutOptions", |gui| {
let mut options = self.data.options();
show_options(&mut options, gui);
self.data.set_options(options);
});
region.foldable("Style", |gui| { region.foldable("Style", |gui| {
show_style(&mut self.style, gui); let mut style = self.data.style();
show_style(&mut style, gui);
self.data.set_style(style);
}); });
region.foldable("Fonts", |gui| { region.foldable("Fonts", |gui| {

View file

@ -8,49 +8,13 @@ use crate::{
font::TextFragment, font::TextFragment,
fonts::{Fonts, TextStyle}, fonts::{Fonts, TextStyle},
math::*, math::*,
style::Style,
types::*, types::*,
widgets::{Label, Widget}, widgets::{Label, Widget},
}; };
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
#[derive(Clone, Copy, Debug, Serialize)]
pub struct LayoutOptions {
/// Horizontal and vertical padding within a window frame.
pub window_padding: Vec2,
/// Button size is text size plus this on each side
pub button_padding: Vec2,
/// Horizontal and vertical spacing between widgets
pub item_spacing: Vec2,
/// Indent foldable regions etc by this much.
pub indent: f32,
/// Anything clickable is (at least) this wide.
pub clickable_diameter: f32,
/// Checkboxes, radio button and foldables have an icon at the start.
/// The text starts after this many pixels.
pub start_icon_width: f32,
}
impl Default for LayoutOptions {
fn default() -> Self {
LayoutOptions {
window_padding: vec2(6.0, 6.0),
button_padding: vec2(5.0, 3.0),
item_spacing: vec2(8.0, 4.0),
indent: 21.0,
clickable_diameter: 34.0,
start_icon_width: 20.0,
}
}
}
// ----------------------------------------------------------------------------
// TODO: rename GuiResponse // TODO: rename GuiResponse
pub struct GuiResponse { pub struct GuiResponse {
/// The mouse is hovering above this /// The mouse is hovering above this
@ -168,10 +132,10 @@ impl GraphicLayers {
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// TODO: give a better name. // TODO: give a better name.
/// Contains the input, options and output of all GUI commands. /// Contains the input, style and output of all GUI commands.
pub struct Data { pub struct Data {
/// The default options for new regions /// The default style for new regions
pub(crate) options: Mutex<LayoutOptions>, pub(crate) style: Mutex<Style>,
pub(crate) fonts: Arc<Fonts>, pub(crate) fonts: Arc<Fonts>,
pub(crate) input: GuiInput, pub(crate) input: GuiInput,
pub(crate) memory: Mutex<Memory>, pub(crate) memory: Mutex<Memory>,
@ -181,7 +145,7 @@ pub struct Data {
impl Clone for Data { impl Clone for Data {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Data { Data {
options: Mutex::new(self.options()), style: Mutex::new(self.style()),
fonts: self.fonts.clone(), fonts: self.fonts.clone(),
input: self.input, input: self.input,
memory: Mutex::new(self.memory.lock().unwrap().clone()), memory: Mutex::new(self.memory.lock().unwrap().clone()),
@ -193,7 +157,7 @@ impl Clone for Data {
impl Data { impl Data {
pub fn new(pixels_per_point: f32) -> Data { pub fn new(pixels_per_point: f32) -> Data {
Data { Data {
options: Default::default(), style: Default::default(),
fonts: Arc::new(Fonts::new(pixels_per_point)), fonts: Arc::new(Fonts::new(pixels_per_point)),
input: Default::default(), input: Default::default(),
memory: Default::default(), memory: Default::default(),
@ -205,12 +169,12 @@ impl Data {
&self.input &self.input
} }
pub fn options(&self) -> LayoutOptions { pub fn style(&self) -> Style {
*self.options.lock().unwrap() *self.style.lock().unwrap()
} }
pub fn set_options(&self, options: LayoutOptions) { pub fn set_style(&self, style: Style) {
*self.options.lock().unwrap() = options; *self.style.lock().unwrap() = style;
} }
// TODO: move // TODO: move
@ -235,12 +199,12 @@ where
// TODO: nicer way to do layering! // TODO: nicer way to do layering!
let num_graphics_before = data.graphics.lock().unwrap().graphics.len(); let num_graphics_before = data.graphics.lock().unwrap().graphics.len();
let options = data.options(); let style = data.style();
let window_padding = options.window_padding; let window_padding = style.window_padding;
let mut popup_region = Region { let mut popup_region = Region {
data: data.clone(), data: data.clone(),
options, style,
id: Default::default(), id: Default::default(),
dir: Direction::Vertical, dir: Direction::Vertical,
align: Align::Min, align: Align::Min,
@ -252,7 +216,7 @@ where
add_contents(&mut popup_region); add_contents(&mut popup_region);
// TODO: handle the last item_spacing in a nicer way // TODO: handle the last item_spacing in a nicer way
let inner_size = popup_region.bounding_size - options.item_spacing; let inner_size = popup_region.bounding_size - style.item_spacing;
let outer_size = inner_size + 2.0 * window_padding; let outer_size = inner_size + 2.0 * window_padding;
let rect = Rect::from_min_size(window_pos, outer_size); let rect = Rect::from_min_size(window_pos, outer_size);
@ -271,7 +235,7 @@ where
pub struct Region { pub struct Region {
pub(crate) data: Arc<Data>, pub(crate) data: Arc<Data>,
pub(crate) options: LayoutOptions, pub(crate) style: Style,
/// Unique ID of this region. /// Unique ID of this region.
pub(crate) id: Id, pub(crate) id: Id,
@ -301,9 +265,13 @@ impl Region {
self.data.graphics.lock().unwrap().graphics.push(gui_cmd) self.data.graphics.lock().unwrap().graphics.push(gui_cmd)
} }
pub fn add_paint_cmd(&mut self, paint_cmd: PaintCmd) {
self.add_graphic(GuiCmd::PaintCommands(vec![paint_cmd]))
}
/// Options for this region, and any child regions we may spawn. /// Options for this region, and any child regions we may spawn.
pub fn options(&self) -> &LayoutOptions { pub fn style(&self) -> &Style {
&self.options &self.style
} }
pub fn data(&self) -> &Arc<Data> { pub fn data(&self) -> &Arc<Data> {
@ -347,11 +315,11 @@ impl Region {
let text_style = TextStyle::Button; let text_style = TextStyle::Button;
let font = &self.fonts()[text_style]; let font = &self.fonts()[text_style];
let (text, text_size) = font.layout_multiline(&text, self.width()); let (text, text_size) = font.layout_multiline(&text, self.width());
let text_cursor = self.cursor + self.options().button_padding; let text_cursor = self.cursor + self.style().button_padding;
let interact = self.reserve_space( let interact = self.reserve_space(
vec2( vec2(
self.available_space.x, self.available_space.x,
text_size.y + 2.0 * self.options().button_padding.y, text_size.y + 2.0 * self.style().button_padding.y,
), ),
Some(id), Some(id),
); );
@ -370,7 +338,7 @@ impl Region {
self.add_graphic(GuiCmd::FoldableHeader { interact, open }); self.add_graphic(GuiCmd::FoldableHeader { interact, open });
self.add_text( self.add_text(
text_cursor + vec2(self.options().start_icon_width, 0.0), text_cursor + vec2(self.style().start_icon_width, 0.0),
text_style, text_style,
text, text,
None, None,
@ -391,10 +359,10 @@ impl Region {
where where
F: FnOnce(&mut Region), F: FnOnce(&mut Region),
{ {
let indent = vec2(self.options().indent, 0.0); let indent = vec2(self.style().indent, 0.0);
let mut child_region = Region { let mut child_region = Region {
data: self.data.clone(), data: self.data.clone(),
options: self.options, style: self.style,
id: self.id, id: self.id,
dir: self.dir, dir: self.dir,
align: Align::Min, align: Align::Min,
@ -411,7 +379,7 @@ impl Region {
pub fn centered_column(&mut self, width: f32, align: Align) -> Region { pub fn centered_column(&mut self, width: f32, align: Align) -> Region {
Region { Region {
data: self.data.clone(), data: self.data.clone(),
options: self.options, style: self.style,
id: self.id, id: self.id,
dir: self.dir, dir: self.dir,
cursor: self.cursor + vec2((self.available_space.x - width) / 2.0, 0.0), cursor: self.cursor + vec2((self.available_space.x - width) / 2.0, 0.0),
@ -427,7 +395,7 @@ impl Region {
{ {
let mut child_region = Region { let mut child_region = Region {
data: self.data.clone(), data: self.data.clone(),
options: self.options, style: self.style,
id: self.id, id: self.id,
dir, dir,
align, align,
@ -467,14 +435,14 @@ impl Region {
F: FnOnce(&mut [Region]) -> R, F: FnOnce(&mut [Region]) -> R,
{ {
// TODO: ensure there is space // TODO: ensure there is space
let padding = self.options().item_spacing.x; let padding = self.style().item_spacing.x;
let total_padding = padding * (num_columns as f32 - 1.0); let total_padding = padding * (num_columns as f32 - 1.0);
let column_width = (self.available_space.x - total_padding) / (num_columns as f32); let column_width = (self.available_space.x - total_padding) / (num_columns as f32);
let mut columns: Vec<Region> = (0..num_columns) let mut columns: Vec<Region> = (0..num_columns)
.map(|col_idx| Region { .map(|col_idx| Region {
data: self.data.clone(), data: self.data.clone(),
options: self.options, style: self.style,
id: self.make_child_id(&("column", col_idx)), id: self.make_child_id(&("column", col_idx)),
dir: Direction::Vertical, dir: Direction::Vertical,
align: self.align, align: self.align,
@ -505,7 +473,7 @@ impl Region {
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
pub fn reserve_space(&mut self, size: Vec2, interaction_id: Option<Id>) -> InteractInfo { pub fn reserve_space(&mut self, size: Vec2, interaction_id: Option<Id>) -> InteractInfo {
let pos = self.reserve_space_without_padding(size + self.options().item_spacing); let pos = self.reserve_space_without_padding(size + self.style().item_spacing);
let rect = Rect::from_min_size(pos, size); let rect = Rect::from_min_size(pos, size);
let mut memory = self.data.memory.lock().unwrap(); let mut memory = self.data.memory.lock().unwrap();

View file

@ -20,7 +20,7 @@ pub mod widgets;
pub use crate::{ pub use crate::{
emigui::Emigui, emigui::Emigui,
fonts::{FontSizes, Fonts, TextStyle}, fonts::{FontSizes, Fonts, TextStyle},
layout::{Align, LayoutOptions, Region}, layout::{Align, Region},
mesher::{Mesh, Vertex}, mesher::{Mesh, Vertex},
style::Style, style::Style,
texture_atlas::Texture, texture_atlas::Texture,

View file

@ -1,18 +1,41 @@
use crate::{math::*, types::*}; use crate::{math::*, types::*};
#[derive(Clone, Debug)] #[derive(Clone, Copy, Debug, Serialize)]
pub struct Style { pub struct Style {
/// Show rectangles around each widget /// Horizontal and vertical padding within a window frame.
pub debug_rects: bool, pub window_padding: Vec2,
/// Button size is text size plus this on each side
pub button_padding: Vec2,
/// Horizontal and vertical spacing between widgets
pub item_spacing: Vec2,
/// Indent foldable regions etc by this much.
pub indent: f32,
/// Anything clickable is (at least) this wide.
pub clickable_diameter: f32,
/// Checkboxes, radio button and foldables have an icon at the start.
/// The text starts after this many pixels.
pub start_icon_width: f32,
// -----------------------------------------------
// Purely visual:
/// For stuff like check marks in check boxes. /// For stuff like check marks in check boxes.
pub line_width: f32, pub line_width: f32,
} }
impl Default for Style { impl Default for Style {
fn default() -> Style { fn default() -> Self {
Style { Style {
debug_rects: false, window_padding: vec2(6.0, 6.0),
button_padding: vec2(5.0, 3.0),
item_spacing: vec2(8.0, 4.0),
indent: 21.0,
clickable_diameter: 34.0,
start_icon_width: 20.0,
line_width: 2.0, line_width: 2.0,
} }
} }
@ -64,19 +87,8 @@ impl Style {
} }
} }
fn debug_rect(rect: Rect) -> PaintCmd { // ----------------------------------------------------------------------------
PaintCmd::Rect {
corner_radius: 0.0,
fill_color: None,
outline: Some(Outline {
color: gray(255, 255),
width: 1.0,
}),
rect,
}
}
/// TODO: a Style struct which defines colors etc
fn translate_cmd(out_commands: &mut Vec<PaintCmd>, style: &Style, 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),
@ -87,9 +99,6 @@ fn translate_cmd(out_commands: &mut Vec<PaintCmd>, style: &Style, cmd: GuiCmd) {
outline: None, outline: None,
rect: interact.rect, rect: interact.rect,
}); });
if style.debug_rects {
out_commands.push(debug_rect(interact.rect));
}
} }
GuiCmd::Checkbox { checked, interact } => { GuiCmd::Checkbox { checked, interact } => {
let (small_icon_rect, big_icon_rect) = style.icon_rectangles(&interact.rect); let (small_icon_rect, big_icon_rect) = style.icon_rectangles(&interact.rect);
@ -113,10 +122,6 @@ fn translate_cmd(out_commands: &mut Vec<PaintCmd>, style: &Style, cmd: GuiCmd) {
width: style.line_width, width: style.line_width,
}); });
} }
if style.debug_rects {
out_commands.push(debug_rect(interact.rect));
}
} }
GuiCmd::FoldableHeader { interact, open } => { GuiCmd::FoldableHeader { interact, open } => {
let fill_color = style.interact_fill_color(&interact); let fill_color = style.interact_fill_color(&interact);
@ -172,10 +177,6 @@ fn translate_cmd(out_commands: &mut Vec<PaintCmd>, style: &Style, cmd: GuiCmd) {
radius: small_icon_rect.size.x / 2.0, radius: small_icon_rect.size.x / 2.0,
}); });
} }
if style.debug_rects {
out_commands.push(debug_rect(interact.rect));
}
} }
GuiCmd::Slider { GuiCmd::Slider {
interact, interact,
@ -208,10 +209,6 @@ fn translate_cmd(out_commands: &mut Vec<PaintCmd>, style: &Style, cmd: GuiCmd) {
}), }),
radius: thickness / 3.0, radius: thickness / 3.0,
}); });
if style.debug_rects {
out_commands.push(debug_rect(rect));
}
} }
GuiCmd::Text { GuiCmd::Text {
color, color,

View file

@ -86,9 +86,9 @@ impl Widget for Button {
let text_style = TextStyle::Button; let text_style = TextStyle::Button;
let font = &region.fonts()[text_style]; let font = &region.fonts()[text_style];
let (text, text_size) = font.layout_multiline(&self.text, region.width()); let (text, text_size) = font.layout_multiline(&self.text, region.width());
let padding = region.options().button_padding; let padding = region.style().button_padding;
let mut size = text_size + 2.0 * padding; let mut size = text_size + 2.0 * padding;
size.y = size.y.max(region.options().clickable_diameter); size.y = size.y.max(region.style().clickable_diameter);
let interact = region.reserve_space(size, Some(id)); let interact = region.reserve_space(size, Some(id));
let text_cursor = interact.rect.left_center() + vec2(padding.x, -0.5 * text_size.y); let text_cursor = interact.rect.left_center() + vec2(padding.x, -0.5 * text_size.y);
region.add_graphic(GuiCmd::Button { interact }); region.add_graphic(GuiCmd::Button { interact });
@ -128,15 +128,15 @@ impl<'a> Widget for Checkbox<'a> {
let font = &region.fonts()[text_style]; let font = &region.fonts()[text_style];
let (text, text_size) = font.layout_multiline(&self.text, region.width()); let (text, text_size) = font.layout_multiline(&self.text, region.width());
let interact = region.reserve_space( let interact = region.reserve_space(
region.options().button_padding region.style().button_padding
+ vec2(region.options().start_icon_width, 0.0) + vec2(region.style().start_icon_width, 0.0)
+ text_size + text_size
+ region.options().button_padding, + region.style().button_padding,
Some(id), Some(id),
); );
let text_cursor = interact.rect.min() let text_cursor = interact.rect.min()
+ region.options().button_padding + region.style().button_padding
+ vec2(region.options().start_icon_width, 0.0); + vec2(region.style().start_icon_width, 0.0);
if interact.clicked { if interact.clicked {
*self.checked = !*self.checked; *self.checked = !*self.checked;
} }
@ -184,15 +184,15 @@ impl Widget for RadioButton {
let font = &region.fonts()[text_style]; let font = &region.fonts()[text_style];
let (text, text_size) = font.layout_multiline(&self.text, region.width()); let (text, text_size) = font.layout_multiline(&self.text, region.width());
let interact = region.reserve_space( let interact = region.reserve_space(
region.options().button_padding region.style().button_padding
+ vec2(region.options().start_icon_width, 0.0) + vec2(region.style().start_icon_width, 0.0)
+ text_size + text_size
+ region.options().button_padding, + region.style().button_padding,
Some(id), Some(id),
); );
let text_cursor = interact.rect.min() let text_cursor = interact.rect.min()
+ region.options().button_padding + region.style().button_padding
+ vec2(region.options().start_icon_width, 0.0); + vec2(region.style().start_icon_width, 0.0);
region.add_graphic(GuiCmd::RadioButton { region.add_graphic(GuiCmd::RadioButton {
checked: self.checked, checked: self.checked,
interact, interact,
@ -323,7 +323,7 @@ impl<'a> Widget for Slider<'a> {
}) })
} }
} else { } else {
let height = font.line_spacing().max(region.options().clickable_diameter); let height = font.line_spacing().max(region.style().clickable_diameter);
let min = self.min; let min = self.min;
let max = self.max; let max = self.max;