Simplify and improve the default visual style
This commit is contained in:
parent
01568acef2
commit
037b22be7f
17 changed files with 393 additions and 189 deletions
|
@ -53,6 +53,7 @@ pub const fn additive_gray(l: u8) -> Color {
|
||||||
pub const TRANSPARENT: Color = srgba(0, 0, 0, 0);
|
pub const TRANSPARENT: Color = srgba(0, 0, 0, 0);
|
||||||
pub const BLACK: Color = srgba(0, 0, 0, 255);
|
pub const BLACK: Color = srgba(0, 0, 0, 255);
|
||||||
pub const LIGHT_GRAY: Color = srgba(220, 220, 220, 255);
|
pub const LIGHT_GRAY: Color = srgba(220, 220, 220, 255);
|
||||||
|
pub const GRAY: Color = srgba(160, 160, 160, 255);
|
||||||
pub const WHITE: Color = srgba(255, 255, 255, 255);
|
pub const WHITE: Color = srgba(255, 255, 255, 255);
|
||||||
pub const RED: Color = srgba(255, 0, 0, 255);
|
pub const RED: Color = srgba(255, 0, 0, 255);
|
||||||
pub const GREEN: Color = srgba(0, 255, 0, 255);
|
pub const GREEN: Color = srgba(0, 255, 0, 255);
|
||||||
|
|
|
@ -63,7 +63,8 @@ impl CollapsingHeader {
|
||||||
let text_pos = available.min + vec2(ui.style().indent, 0.0);
|
let text_pos = available.min + vec2(ui.style().indent, 0.0);
|
||||||
let galley = label.layout(available.width() - ui.style().indent, ui);
|
let galley = label.layout(available.width() - ui.style().indent, ui);
|
||||||
let text_max_x = text_pos.x + galley.size.x;
|
let text_max_x = text_pos.x + galley.size.x;
|
||||||
let desired_width = available.width().max(text_max_x - available.left());
|
let desired_width = text_max_x - available.left();
|
||||||
|
let desired_width = desired_width.max(available.width());
|
||||||
|
|
||||||
let interact = ui.reserve_space(
|
let interact = ui.reserve_space(
|
||||||
vec2(
|
vec2(
|
||||||
|
@ -87,9 +88,19 @@ impl CollapsingHeader {
|
||||||
*state
|
*state
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let animation_time = ui.style().animation_time;
|
||||||
|
let time_since_toggle = (ui.input().time - state.toggle_time) as f32;
|
||||||
|
let time_since_toggle = time_since_toggle + ui.input().dt; // Instant feedback
|
||||||
|
let openness = if state.open {
|
||||||
|
remap_clamp(time_since_toggle, 0.0..=animation_time, 0.0..=1.0)
|
||||||
|
} else {
|
||||||
|
remap_clamp(time_since_toggle, 0.0..=animation_time, 1.0..=0.0)
|
||||||
|
};
|
||||||
|
let animate = time_since_toggle < animation_time;
|
||||||
|
|
||||||
let where_to_put_background = ui.paint_list_len();
|
let where_to_put_background = ui.paint_list_len();
|
||||||
|
|
||||||
paint_icon(ui, &state, &interact);
|
paint_icon(ui, &interact, openness);
|
||||||
|
|
||||||
ui.add_galley(
|
ui.add_galley(
|
||||||
text_pos,
|
text_pos,
|
||||||
|
@ -102,18 +113,14 @@ impl CollapsingHeader {
|
||||||
where_to_put_background,
|
where_to_put_background,
|
||||||
PaintCmd::Rect {
|
PaintCmd::Rect {
|
||||||
corner_radius: ui.style().interact(&interact).corner_radius,
|
corner_radius: ui.style().interact(&interact).corner_radius,
|
||||||
fill_color: ui.style().interact(&interact).fill_color,
|
fill_color: ui.style().interact(&interact).bg_fill_color,
|
||||||
outline: ui.style().interact(&interact).outline,
|
outline: None,
|
||||||
rect: interact.rect,
|
rect: interact.rect,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
ui.expand_to_include_child(interact.rect); // TODO: remove, just a test
|
ui.expand_to_include_child(interact.rect); // TODO: remove, just a test
|
||||||
|
|
||||||
let animation_time = ui.style().animation_time;
|
|
||||||
let time_since_toggle = (ui.input().time - state.toggle_time) as f32;
|
|
||||||
let time_since_toggle = time_since_toggle + ui.input().dt; // Instant feedback
|
|
||||||
let animate = time_since_toggle < animation_time;
|
|
||||||
if animate {
|
if animate {
|
||||||
ui.indent(id, |child_ui| {
|
ui.indent(id, |child_ui| {
|
||||||
let max_height = if state.open {
|
let max_height = if state.open {
|
||||||
|
@ -154,7 +161,7 @@ impl CollapsingHeader {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paint_icon(ui: &mut Ui, state: &State, interact: &InteractInfo) {
|
fn paint_icon(ui: &mut Ui, interact: &InteractInfo, openness: f32) {
|
||||||
let stroke_color = ui.style().interact(interact).stroke_color;
|
let stroke_color = ui.style().interact(interact).stroke_color;
|
||||||
let stroke_width = ui.style().interact(interact).stroke_width;
|
let stroke_width = ui.style().interact(interact).stroke_width;
|
||||||
|
|
||||||
|
@ -164,25 +171,24 @@ fn paint_icon(ui: &mut Ui, state: &State, interact: &InteractInfo) {
|
||||||
interact.rect.center().y,
|
interact.rect.center().y,
|
||||||
));
|
));
|
||||||
|
|
||||||
// Draw a minus:
|
// Draw a pointy triangle arrow:
|
||||||
ui.add_paint_cmd(PaintCmd::LineSegment {
|
let rect = Rect::from_center_size(
|
||||||
points: [
|
small_icon_rect.center(),
|
||||||
pos2(small_icon_rect.left(), small_icon_rect.center().y),
|
vec2(small_icon_rect.width(), small_icon_rect.height()) * 0.75,
|
||||||
pos2(small_icon_rect.right(), small_icon_rect.center().y),
|
);
|
||||||
],
|
let mut points = [rect.left_top(), rect.right_top(), rect.center_bottom()];
|
||||||
color: stroke_color,
|
let rotation = Vec2::angled(remap(openness, 0.0..=1.0, -TAU / 4.0..=0.0));
|
||||||
width: stroke_width,
|
for p in &mut points {
|
||||||
});
|
let v = *p - rect.center();
|
||||||
|
let v = rotation.rotate_other(v);
|
||||||
if !state.open {
|
*p = rect.center() + v;
|
||||||
// Draw it as a plus:
|
|
||||||
ui.add_paint_cmd(PaintCmd::LineSegment {
|
|
||||||
points: [
|
|
||||||
pos2(small_icon_rect.center().x, small_icon_rect.top()),
|
|
||||||
pos2(small_icon_rect.center().x, small_icon_rect.bottom()),
|
|
||||||
],
|
|
||||||
color: stroke_color,
|
|
||||||
width: stroke_width,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
// }
|
||||||
|
|
||||||
|
ui.add_paint_cmd(PaintCmd::Path {
|
||||||
|
path: mesher::Path::from_point_loop(&points),
|
||||||
|
closed: true,
|
||||||
|
fill_color: None,
|
||||||
|
outline: Some(Outline::new(stroke_width, stroke_color)),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,16 +15,25 @@ impl Frame {
|
||||||
Self {
|
Self {
|
||||||
margin: style.window_padding,
|
margin: style.window_padding,
|
||||||
corner_radius: style.window.corner_radius,
|
corner_radius: style.window.corner_radius,
|
||||||
fill_color: Some(style.background_fill_color()),
|
fill_color: Some(style.background_fill_color),
|
||||||
outline: Some(Outline::new(1.0, color::WHITE)),
|
outline: Some(Outline::new(1.0, color::WHITE)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn menu_bar(_style: &Style) -> Self {
|
||||||
|
Self {
|
||||||
|
margin: Vec2::splat(1.0),
|
||||||
|
corner_radius: 0.0,
|
||||||
|
fill_color: None,
|
||||||
|
outline: Some(Outline::new(0.5, color::white(128))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn menu(style: &Style) -> Self {
|
pub fn menu(style: &Style) -> Self {
|
||||||
Self {
|
Self {
|
||||||
margin: Vec2::splat(1.0),
|
margin: Vec2::splat(1.0),
|
||||||
corner_radius: 2.0,
|
corner_radius: 2.0,
|
||||||
fill_color: Some(style.background_fill_color()),
|
fill_color: Some(style.background_fill_color),
|
||||||
outline: Some(Outline::new(1.0, color::white(128))),
|
outline: Some(Outline::new(1.0, color::white(128))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,7 +42,7 @@ impl Frame {
|
||||||
Self {
|
Self {
|
||||||
margin: style.window_padding,
|
margin: style.window_padding,
|
||||||
corner_radius: 5.0,
|
corner_radius: 5.0,
|
||||||
fill_color: Some(style.background_fill_color()),
|
fill_color: Some(style.background_fill_color),
|
||||||
outline: Some(Outline::new(1.0, color::white(128))),
|
outline: Some(Outline::new(1.0, color::white(128))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,13 +21,16 @@ impl Default for BarState {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn bar(ui: &mut Ui, add_contents: impl FnOnce(&mut Ui)) -> InteractInfo {
|
pub fn bar(ui: &mut Ui, add_contents: impl FnOnce(&mut Ui)) -> InteractInfo {
|
||||||
ui.horizontal(|ui| {
|
ui.inner_layout(Layout::horizontal(Align::Center), |ui| {
|
||||||
Frame::default().show(ui, |ui| {
|
Frame::menu_bar(ui.style()).show(ui, |ui| {
|
||||||
let mut style = ui.style().clone();
|
let mut style = ui.style().clone();
|
||||||
style.button_padding = vec2(2.0, 0.0);
|
style.button_padding = vec2(2.0, 0.0);
|
||||||
style.interact.inactive.fill_color = None;
|
// style.interact.active.bg_fill_color = None;
|
||||||
style.interact.inactive.outline = None;
|
style.interact.active.rect_outline = None;
|
||||||
style.interact.hovered.fill_color = None;
|
// style.interact.hovered.bg_fill_color = None;
|
||||||
|
style.interact.hovered.rect_outline = None;
|
||||||
|
style.interact.inactive.bg_fill_color = None;
|
||||||
|
style.interact.inactive.rect_outline = None;
|
||||||
ui.set_style(style);
|
ui.set_style(style);
|
||||||
|
|
||||||
// Take full width and fixed height:
|
// Take full width and fixed height:
|
||||||
|
@ -55,7 +58,7 @@ pub fn menu(ui: &mut Ui, title: impl Into<String>, add_contents: impl FnOnce(&mu
|
||||||
let mut button = Button::new(title);
|
let mut button = Button::new(title);
|
||||||
|
|
||||||
if bar_state.open_menu == Some(menu_id) {
|
if bar_state.open_menu == Some(menu_id) {
|
||||||
button = button.fill_color(ui.style().interact.active.fill_color);
|
button = button.fill_color(Some(ui.style().interact.active.fill_color));
|
||||||
}
|
}
|
||||||
|
|
||||||
let button_interact = ui.add(button);
|
let button_interact = ui.add(button);
|
||||||
|
@ -75,11 +78,12 @@ pub fn menu(ui: &mut Ui, title: impl Into<String>, add_contents: impl FnOnce(&mu
|
||||||
resize.show(ui, |ui| {
|
resize.show(ui, |ui| {
|
||||||
let mut style = ui.style().clone();
|
let mut style = ui.style().clone();
|
||||||
style.button_padding = vec2(2.0, 0.0);
|
style.button_padding = vec2(2.0, 0.0);
|
||||||
style.interact.inactive.fill_color = None;
|
// style.interact.active.bg_fill_color = None;
|
||||||
style.interact.inactive.outline = None;
|
style.interact.active.rect_outline = None;
|
||||||
style.interact.active.corner_radius = 0.0;
|
// style.interact.hovered.bg_fill_color = None;
|
||||||
style.interact.hovered.corner_radius = 0.0;
|
style.interact.hovered.rect_outline = None;
|
||||||
style.interact.inactive.corner_radius = 0.0;
|
style.interact.inactive.bg_fill_color = None;
|
||||||
|
style.interact.inactive.rect_outline = None;
|
||||||
ui.set_style(style);
|
ui.set_style(style);
|
||||||
ui.set_layout(Layout::justified(Direction::Vertical));
|
ui.set_layout(Layout::justified(Direction::Vertical));
|
||||||
add_contents(ui)
|
add_contents(ui)
|
||||||
|
|
|
@ -27,6 +27,7 @@ pub struct Resize {
|
||||||
expand_width_to_fit_content: bool,
|
expand_width_to_fit_content: bool,
|
||||||
expand_height_to_fit_content: bool,
|
expand_height_to_fit_content: bool,
|
||||||
|
|
||||||
|
outline: bool,
|
||||||
handle_offset: Vec2,
|
handle_offset: Vec2,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,13 +35,14 @@ impl Default for Resize {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
resizable: true,
|
resizable: true,
|
||||||
min_size: Vec2::splat(32.0),
|
min_size: Vec2::splat(16.0),
|
||||||
max_size: Vec2::infinity(),
|
max_size: Vec2::infinity(),
|
||||||
default_size: vec2(f32::INFINITY, 200.0), // TODO
|
default_size: vec2(f32::INFINITY, 200.0), // TODO
|
||||||
auto_shrink_width: false,
|
auto_shrink_width: false,
|
||||||
auto_shrink_height: false,
|
auto_shrink_height: false,
|
||||||
expand_width_to_fit_content: true,
|
expand_width_to_fit_content: true,
|
||||||
expand_height_to_fit_content: true,
|
expand_height_to_fit_content: true,
|
||||||
|
outline: true,
|
||||||
handle_offset: Default::default(),
|
handle_offset: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -128,12 +130,6 @@ impl Resize {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Offset the position of the resize handle by this much
|
|
||||||
pub fn handle_offset(mut self, handle_offset: Vec2) -> Self {
|
|
||||||
self.handle_offset = handle_offset;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn auto_shrink_width(mut self, auto_shrink_width: bool) -> Self {
|
pub fn auto_shrink_width(mut self, auto_shrink_width: bool) -> Self {
|
||||||
self.auto_shrink_width = auto_shrink_width;
|
self.auto_shrink_width = auto_shrink_width;
|
||||||
self
|
self
|
||||||
|
@ -143,6 +139,17 @@ impl Resize {
|
||||||
self.auto_shrink_height = auto_shrink_height;
|
self.auto_shrink_height = auto_shrink_height;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Offset the position of the resize handle by this much
|
||||||
|
pub fn handle_offset(mut self, handle_offset: Vec2) -> Self {
|
||||||
|
self.handle_offset = handle_offset;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn outline(mut self, outline: bool) -> Self {
|
||||||
|
self.outline = outline;
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: a common trait for Things that follow this pattern
|
// TODO: a common trait for Things that follow this pattern
|
||||||
|
@ -240,6 +247,17 @@ impl Resize {
|
||||||
|
|
||||||
// ------------------------------
|
// ------------------------------
|
||||||
|
|
||||||
|
if self.outline && corner_interact.is_some() {
|
||||||
|
let rect = Rect::from_min_size(position, state.size);
|
||||||
|
let rect = rect.expand(2.0); // breathing room for content
|
||||||
|
ui.add_paint_cmd(PaintCmd::Rect {
|
||||||
|
rect,
|
||||||
|
corner_radius: 3.0,
|
||||||
|
fill_color: None,
|
||||||
|
outline: Some(ui.style().thin_outline),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(corner_interact) = corner_interact {
|
if let Some(corner_interact) = corner_interact {
|
||||||
paint_resize_corner(ui, &corner_interact);
|
paint_resize_corner(ui, &corner_interact);
|
||||||
|
|
||||||
|
|
|
@ -165,19 +165,19 @@ impl ScrollArea {
|
||||||
|
|
||||||
let style = outer_ui.style();
|
let style = outer_ui.style();
|
||||||
let handle_fill_color = style.interact(&handle_interact).fill_color;
|
let handle_fill_color = style.interact(&handle_interact).fill_color;
|
||||||
let handle_outline = style.interact(&handle_interact).outline;
|
let handle_outline = style.interact(&handle_interact).rect_outline;
|
||||||
|
|
||||||
outer_ui.add_paint_cmd(PaintCmd::Rect {
|
outer_ui.add_paint_cmd(PaintCmd::Rect {
|
||||||
rect: outer_scroll_rect,
|
rect: outer_scroll_rect,
|
||||||
corner_radius,
|
corner_radius,
|
||||||
fill_color: Some(color::gray(0, 196)), // TODO style
|
fill_color: Some(outer_ui.style().dark_bg_color),
|
||||||
outline: None,
|
outline: None,
|
||||||
});
|
});
|
||||||
|
|
||||||
outer_ui.add_paint_cmd(PaintCmd::Rect {
|
outer_ui.add_paint_cmd(PaintCmd::Rect {
|
||||||
rect: handle_rect.expand(-2.0),
|
rect: handle_rect.expand(-2.0),
|
||||||
corner_radius,
|
corner_radius,
|
||||||
fill_color: handle_fill_color,
|
fill_color: Some(handle_fill_color),
|
||||||
outline: handle_outline,
|
outline: handle_outline,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,11 +28,12 @@ impl<'open> Window<'open> {
|
||||||
area,
|
area,
|
||||||
frame: None,
|
frame: None,
|
||||||
resize: Resize::default()
|
resize: Resize::default()
|
||||||
.handle_offset(Vec2::splat(4.0))
|
.auto_expand_height(false)
|
||||||
.auto_shrink_width(true)
|
|
||||||
.auto_expand_width(true)
|
.auto_expand_width(true)
|
||||||
.auto_shrink_height(false)
|
.auto_shrink_height(false)
|
||||||
.auto_expand_height(false),
|
.auto_shrink_width(true)
|
||||||
|
.handle_offset(Vec2::splat(4.0))
|
||||||
|
.outline(false),
|
||||||
scroll: Some(
|
scroll: Some(
|
||||||
ScrollArea::default()
|
ScrollArea::default()
|
||||||
.always_show_scroll(false)
|
.always_show_scroll(false)
|
||||||
|
@ -137,28 +138,89 @@ impl<'open> Window<'open> {
|
||||||
|
|
||||||
let frame = frame.unwrap_or_else(|| Frame::window(&ctx.style()));
|
let frame = frame.unwrap_or_else(|| Frame::window(&ctx.style()));
|
||||||
|
|
||||||
// TODO: easier way to compose these
|
if true {
|
||||||
area.show(ctx, |ui| {
|
// TODO: easier way to compose these
|
||||||
frame.show(ui, |ui| {
|
area.show(ctx, |ui| {
|
||||||
resize.show(ui, |ui| {
|
frame.show(ui, |ui| {
|
||||||
ui.horizontal(|ui| {
|
resize.show(ui, |ui| {
|
||||||
// TODO: prettier close button, and to the right of the window
|
show_title_bar(ui, title_label, open);
|
||||||
if let Some(open) = open {
|
if let Some(scroll) = scroll {
|
||||||
if ui.add(Button::new("X")).clicked {
|
scroll.show(ui, add_contents)
|
||||||
*open = false;
|
} else {
|
||||||
}
|
add_contents(ui)
|
||||||
}
|
}
|
||||||
ui.add(title_label);
|
})
|
||||||
});
|
|
||||||
ui.add(Separator::new().line_width(1.0)); // TODO: nicer way to split window title from contents
|
|
||||||
|
|
||||||
if let Some(scroll) = scroll {
|
|
||||||
scroll.show(ui, add_contents)
|
|
||||||
} else {
|
|
||||||
add_contents(ui)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
} else {
|
||||||
|
// TODO: something like this, with collapsing contents
|
||||||
|
area.show(ctx, |ui| {
|
||||||
|
frame.show(ui, |ui| {
|
||||||
|
CollapsingHeader::new(title_label.text()).show(ui, |ui| {
|
||||||
|
resize.show(ui, |ui| {
|
||||||
|
if let Some(scroll) = scroll {
|
||||||
|
scroll.show(ui, add_contents)
|
||||||
|
} else {
|
||||||
|
add_contents(ui)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn show_title_bar(ui: &mut Ui, title_label: Label, open: Option<&mut bool>) {
|
||||||
|
let button_size = ui.style().clickable_diameter;
|
||||||
|
|
||||||
|
// TODO: show collapse button
|
||||||
|
|
||||||
|
let title_rect = ui.add(title_label).rect;
|
||||||
|
|
||||||
|
if let Some(open) = open {
|
||||||
|
let close_max_x = title_rect.right() + ui.style().item_spacing.x + button_size;
|
||||||
|
let close_max_x = close_max_x.max(ui.rect_finite().right());
|
||||||
|
let close_rect = Rect::from_min_size(
|
||||||
|
pos2(
|
||||||
|
close_max_x - button_size,
|
||||||
|
title_rect.center().y - 0.5 * button_size,
|
||||||
|
),
|
||||||
|
Vec2::splat(button_size),
|
||||||
|
);
|
||||||
|
if close_button(ui, close_rect).clicked {
|
||||||
|
*open = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.add(Separator::new().line_width(1.0)); // TODO: nicer way to split window title from contents
|
||||||
|
}
|
||||||
|
|
||||||
|
fn close_button(ui: &mut Ui, rect: Rect) -> InteractInfo {
|
||||||
|
let close_id = ui.make_child_id("window_close_button");
|
||||||
|
let interact = ui.interact_rect(rect, close_id);
|
||||||
|
ui.expand_to_include_child(interact.rect);
|
||||||
|
|
||||||
|
// ui.add_paint_cmd(PaintCmd::Rect {
|
||||||
|
// corner_radius: ui.style().interact(&interact).corner_radius,
|
||||||
|
// fill_color: ui.style().interact(&interact).bg_fill_color,
|
||||||
|
// outline: ui.style().interact(&interact).rect_outline,
|
||||||
|
// rect: interact.rect,
|
||||||
|
// });
|
||||||
|
|
||||||
|
let rect = rect.expand(-4.0);
|
||||||
|
|
||||||
|
let stroke_color = ui.style().interact(&interact).stroke_color;
|
||||||
|
let stroke_width = ui.style().interact(&interact).stroke_width;
|
||||||
|
ui.add_paint_cmd(PaintCmd::line_segment(
|
||||||
|
[rect.left_top(), rect.right_bottom()],
|
||||||
|
stroke_color,
|
||||||
|
stroke_width,
|
||||||
|
));
|
||||||
|
ui.add_paint_cmd(PaintCmd::line_segment(
|
||||||
|
[rect.right_top(), rect.left_bottom()],
|
||||||
|
stroke_color,
|
||||||
|
stroke_width,
|
||||||
|
));
|
||||||
|
interact
|
||||||
|
}
|
||||||
|
|
|
@ -196,6 +196,7 @@ impl Context {
|
||||||
fn paint(&self) -> PaintBatches {
|
fn paint(&self) -> PaintBatches {
|
||||||
let mut mesher_options = *self.mesher_options.lock();
|
let mut mesher_options = *self.mesher_options.lock();
|
||||||
mesher_options.aa_size = 1.0 / self.pixels_per_point();
|
mesher_options.aa_size = 1.0 / self.pixels_per_point();
|
||||||
|
mesher_options.aa_size *= 1.5; // Looks better, but TODO: should not be needed
|
||||||
let paint_commands = self.drain_paint_lists();
|
let paint_commands = self.drain_paint_lists();
|
||||||
let num_primitives = paint_commands.len();
|
let num_primitives = paint_commands.len();
|
||||||
let batches = mesher::mesh_paint_commands(mesher_options, self.fonts(), paint_commands);
|
let batches = mesher::mesh_paint_commands(mesher_options, self.fonts(), paint_commands);
|
||||||
|
@ -429,7 +430,7 @@ impl Context {
|
||||||
text_style: TextStyle,
|
text_style: TextStyle,
|
||||||
color: Option<Color>,
|
color: Option<Color>,
|
||||||
) {
|
) {
|
||||||
let color = color.unwrap_or_else(|| self.style().text_color());
|
let color = color.unwrap_or_else(|| self.style().text_color);
|
||||||
self.add_paint_cmd(
|
self.add_paint_cmd(
|
||||||
layer,
|
layer,
|
||||||
PaintCmd::Text {
|
PaintCmd::Text {
|
||||||
|
|
|
@ -18,7 +18,6 @@ pub struct ExampleApp {
|
||||||
impl ExampleApp {
|
impl ExampleApp {
|
||||||
pub fn ui(&mut self, ui: &mut Ui) {
|
pub fn ui(&mut self, ui: &mut Ui) {
|
||||||
show_menu_bar(ui, &mut self.open_windows);
|
show_menu_bar(ui, &mut self.open_windows);
|
||||||
ui.add(Separator::new());
|
|
||||||
self.windows(ui.ctx());
|
self.windows(ui.ctx());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -279,7 +278,8 @@ struct Widgets {
|
||||||
count: usize,
|
count: usize,
|
||||||
radio: usize,
|
radio: usize,
|
||||||
slider_value: usize,
|
slider_value: usize,
|
||||||
text_inputs: [String; 3],
|
single_line_text_input: String,
|
||||||
|
multiline_text_input: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Widgets {
|
impl Default for Widgets {
|
||||||
|
@ -289,7 +289,8 @@ impl Default for Widgets {
|
||||||
radio: 0,
|
radio: 0,
|
||||||
count: 0,
|
count: 0,
|
||||||
slider_value: 100,
|
slider_value: 100,
|
||||||
text_inputs: ["Hello".to_string(), "World".to_string(), "".to_string()],
|
single_line_text_input: "Hello World!".to_owned(),
|
||||||
|
multiline_text_input: "Text can both be so wide that it needs a linebreak, but you can also add manual linebreak by pressing enter, creating new paragraphs.\nThis is the start of the next paragraph.\n\nClick me to edit me!".to_owned(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -318,7 +319,7 @@ impl Widgets {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ui.horizontal(|ui| {
|
ui.inner_layout(Layout::horizontal(Align::Center), |ui| {
|
||||||
if ui
|
if ui
|
||||||
.add(Button::new("Click me"))
|
.add(Button::new("Click me"))
|
||||||
.tooltip_text("This will just increase a counter.")
|
.tooltip_text("This will just increase a counter.")
|
||||||
|
@ -334,12 +335,17 @@ impl Widgets {
|
||||||
self.slider_value *= 2;
|
self.slider_value *= 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (i, text) in self.text_inputs.iter_mut().enumerate() {
|
ui.horizontal(|ui| {
|
||||||
ui.horizontal(|ui| {
|
ui.add(label!("Single line text input:"));
|
||||||
ui.add(label!("Text input {}: ", i));
|
ui.add(
|
||||||
ui.add(TextEdit::new(text).id(i));
|
TextEdit::new(&mut self.single_line_text_input)
|
||||||
}); // TODO: .tooltip_text("Enter text to edit me")
|
.multiline(false)
|
||||||
}
|
.id("single line"),
|
||||||
|
);
|
||||||
|
}); // TODO: .tooltip_text("Enter text to edit me")
|
||||||
|
|
||||||
|
ui.add(label!("Multiline text input:"));
|
||||||
|
ui.add(TextEdit::new(&mut self.multiline_text_input).id("multiline"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -412,46 +418,42 @@ impl Painting {
|
||||||
self.lines.clear();
|
self.lines.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
ui.add_custom_contents(vec2(f32::INFINITY, 200.0), |ui| {
|
Resize::default()
|
||||||
let interact = ui.reserve_space(ui.available_finite().size(), Some(ui.id()));
|
.default_height(200.0)
|
||||||
let rect = interact.rect;
|
.show(ui, |ui| self.content(ui));
|
||||||
ui.set_clip_rect(ui.clip_rect().intersect(rect)); // Make sure we don't paint out of bounds
|
}
|
||||||
|
|
||||||
if self.lines.is_empty() {
|
fn content(&mut self, ui: &mut Ui) {
|
||||||
self.lines.push(vec![]);
|
let interact = ui.reserve_space(ui.available_finite().size(), Some(ui.id()));
|
||||||
}
|
let rect = interact.rect;
|
||||||
|
ui.set_clip_rect(ui.clip_rect().intersect(rect)); // Make sure we don't paint out of bounds
|
||||||
|
|
||||||
let current_line = self.lines.last_mut().unwrap();
|
if self.lines.is_empty() {
|
||||||
|
self.lines.push(vec![]);
|
||||||
|
}
|
||||||
|
|
||||||
if interact.active {
|
let current_line = self.lines.last_mut().unwrap();
|
||||||
if let Some(mouse_pos) = ui.input().mouse_pos {
|
|
||||||
let canvas_pos = mouse_pos - rect.min;
|
|
||||||
if current_line.last() != Some(&canvas_pos) {
|
|
||||||
current_line.push(canvas_pos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if !current_line.is_empty() {
|
|
||||||
self.lines.push(vec![]);
|
|
||||||
}
|
|
||||||
|
|
||||||
for line in &self.lines {
|
if interact.active {
|
||||||
if line.len() >= 2 {
|
if let Some(mouse_pos) = ui.input().mouse_pos {
|
||||||
ui.add_paint_cmd(PaintCmd::LinePath {
|
let canvas_pos = mouse_pos - rect.min;
|
||||||
points: line.iter().map(|p| rect.min + *p).collect(),
|
if current_line.last() != Some(&canvas_pos) {
|
||||||
color: LIGHT_GRAY,
|
current_line.push(canvas_pos);
|
||||||
width: 2.0,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if !current_line.is_empty() {
|
||||||
|
self.lines.push(vec![]);
|
||||||
|
}
|
||||||
|
|
||||||
// Frame it:
|
for line in &self.lines {
|
||||||
ui.add_paint_cmd(PaintCmd::Rect {
|
if line.len() >= 2 {
|
||||||
rect: ui.rect(),
|
ui.add_paint_cmd(PaintCmd::LinePath {
|
||||||
corner_radius: 0.0,
|
points: line.iter().map(|p| rect.min + *p).collect(),
|
||||||
fill_color: None,
|
color: LIGHT_GRAY,
|
||||||
outline: Some(Outline::new(1.0, WHITE)),
|
width: 2.0,
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,9 +30,9 @@ pub type FontDefinitions = BTreeMap<TextStyle, (FontFamily, f32)>;
|
||||||
|
|
||||||
pub fn default_font_definitions() -> FontDefinitions {
|
pub fn default_font_definitions() -> FontDefinitions {
|
||||||
let mut definitions = FontDefinitions::new();
|
let mut definitions = FontDefinitions::new();
|
||||||
definitions.insert(TextStyle::Body, (FontFamily::VariableWidth, 16.0));
|
definitions.insert(TextStyle::Body, (FontFamily::VariableWidth, 14.0));
|
||||||
definitions.insert(TextStyle::Button, (FontFamily::VariableWidth, 18.0));
|
definitions.insert(TextStyle::Button, (FontFamily::VariableWidth, 16.0));
|
||||||
definitions.insert(TextStyle::Heading, (FontFamily::VariableWidth, 28.0));
|
definitions.insert(TextStyle::Heading, (FontFamily::VariableWidth, 24.0));
|
||||||
definitions.insert(TextStyle::Monospace, (FontFamily::Monospace, 13.0));
|
definitions.insert(TextStyle::Monospace, (FontFamily::Monospace, 13.0));
|
||||||
definitions
|
definitions
|
||||||
}
|
}
|
||||||
|
|
|
@ -143,6 +143,12 @@ pub struct PathPoint {
|
||||||
pub struct Path(Vec<PathPoint>);
|
pub struct Path(Vec<PathPoint>);
|
||||||
|
|
||||||
impl Path {
|
impl Path {
|
||||||
|
pub fn from_point_loop(points: &[Pos2]) -> Self {
|
||||||
|
let mut path = Self::default();
|
||||||
|
path.add_line_loop(points);
|
||||||
|
path
|
||||||
|
}
|
||||||
|
|
||||||
pub fn clear(&mut self) {
|
pub fn clear(&mut self) {
|
||||||
self.0.clear();
|
self.0.clear();
|
||||||
}
|
}
|
||||||
|
@ -153,7 +159,8 @@ impl Path {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_circle(&mut self, center: Pos2, radius: f32) {
|
pub fn add_circle(&mut self, center: Pos2, radius: f32) {
|
||||||
let n = 32; // TODO: parameter
|
let n = (radius * 2.0).round() as i32; // TODO: tweak a bit more
|
||||||
|
let n = clamp(n, 4..=64);
|
||||||
for i in 0..n {
|
for i in 0..n {
|
||||||
let angle = remap(i as f32, 0.0..=n as f32, 0.0..=TAU);
|
let angle = remap(i as f32, 0.0..=n as f32, 0.0..=TAU);
|
||||||
let normal = vec2(angle.cos(), angle.sin());
|
let normal = vec2(angle.cos(), angle.sin());
|
||||||
|
@ -167,6 +174,7 @@ impl Path {
|
||||||
self.add_point(points[1], normal);
|
self.add_point(points[1], normal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: make it clear it is an open (non-closed) thing.
|
||||||
pub fn add_line(&mut self, points: &[Pos2]) {
|
pub fn add_line(&mut self, points: &[Pos2]) {
|
||||||
let n = points.len();
|
let n = points.len();
|
||||||
assert!(n >= 2);
|
assert!(n >= 2);
|
||||||
|
@ -177,8 +185,8 @@ impl Path {
|
||||||
} else {
|
} else {
|
||||||
self.add_point(points[0], (points[1] - points[0]).normalized().rot90());
|
self.add_point(points[0], (points[1] - points[0]).normalized().rot90());
|
||||||
for i in 1..n - 1 {
|
for i in 1..n - 1 {
|
||||||
let n0 = (points[i] - points[i - 1]).normalized().rot90();
|
let n0 = (points[i] - points[i - 1]).normalized().rot90(); // TODO: don't calculate each normal twice!
|
||||||
let n1 = (points[i + 1] - points[i]).normalized().rot90();
|
let n1 = (points[i + 1] - points[i]).normalized().rot90(); // TODO: don't calculate each normal twice!
|
||||||
let v = (n0 + n1) / 2.0;
|
let v = (n0 + n1) / 2.0;
|
||||||
let normal = v / v.length_sq();
|
let normal = v / v.length_sq();
|
||||||
self.add_point(points[i], normal); // TODO: handle VERY sharp turns better
|
self.add_point(points[i], normal); // TODO: handle VERY sharp turns better
|
||||||
|
@ -190,6 +198,20 @@ impl Path {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn add_line_loop(&mut self, points: &[Pos2]) {
|
||||||
|
let n = points.len();
|
||||||
|
assert!(n >= 2);
|
||||||
|
|
||||||
|
// TODO: optimize
|
||||||
|
for i in 0..n {
|
||||||
|
let n0 = (points[i] - points[(i + n - 1) % n]).normalized().rot90();
|
||||||
|
let n1 = (points[(i + 1) % n] - points[i]).normalized().rot90();
|
||||||
|
let v = (n0 + n1) / 2.0;
|
||||||
|
let normal = v / v.length_sq();
|
||||||
|
self.add_point(points[i], normal); // TODO: handle VERY sharp turns better
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn add_rectangle(&mut self, rect: Rect) {
|
pub fn add_rectangle(&mut self, rect: Rect) {
|
||||||
let min = rect.min;
|
let min = rect.min;
|
||||||
let max = rect.max;
|
let max = rect.max;
|
||||||
|
@ -229,7 +251,8 @@ impl Path {
|
||||||
/// quadrant 3 up rigth
|
/// quadrant 3 up rigth
|
||||||
/// 4 * TAU / 4 = right
|
/// 4 * TAU / 4 = right
|
||||||
pub fn add_circle_quadrant(&mut self, center: Pos2, radius: f32, quadrant: f32) {
|
pub fn add_circle_quadrant(&mut self, center: Pos2, radius: f32, quadrant: f32) {
|
||||||
let n = 8;
|
let n = (radius * 0.5).round() as i32; // TODO: tweak a bit more
|
||||||
|
let n = clamp(n, 2..=32);
|
||||||
const RIGHT_ANGLE: f32 = TAU / 4.0;
|
const RIGHT_ANGLE: f32 = TAU / 4.0;
|
||||||
for i in 0..=n {
|
for i in 0..=n {
|
||||||
let angle = remap(
|
let angle = remap(
|
||||||
|
@ -570,6 +593,9 @@ pub fn mesh_command(
|
||||||
color,
|
color,
|
||||||
} => {
|
} => {
|
||||||
galley.sanity_check();
|
galley.sanity_check();
|
||||||
|
|
||||||
|
let text_offset = vec2(0.0, 1.0); // Eye-balled for buttons. TODO: why is this needed?
|
||||||
|
|
||||||
let font = &fonts[text_style];
|
let font = &fonts[text_style];
|
||||||
let mut chars = galley.text.chars();
|
let mut chars = galley.text.chars();
|
||||||
for line in &galley.lines {
|
for line in &galley.lines {
|
||||||
|
@ -577,7 +603,7 @@ pub fn mesh_command(
|
||||||
let c = chars.next().unwrap();
|
let c = chars.next().unwrap();
|
||||||
if let Some(glyph) = font.uv_rect(c) {
|
if let Some(glyph) = font.uv_rect(c) {
|
||||||
let mut top_left = Vertex {
|
let mut top_left = Vertex {
|
||||||
pos: pos + glyph.offset + vec2(*x_offset, line.y_min),
|
pos: pos + glyph.offset + vec2(*x_offset, line.y_min) + text_offset,
|
||||||
uv: glyph.min,
|
uv: glyph.min,
|
||||||
color,
|
color,
|
||||||
};
|
};
|
||||||
|
|
|
@ -4,6 +4,7 @@ use serde_derive::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{color::*, math::*, types::*};
|
use crate::{color::*, math::*, types::*};
|
||||||
|
|
||||||
|
// TODO: split into Spacing and Style?
|
||||||
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
|
||||||
pub struct Style {
|
pub struct Style {
|
||||||
/// Horizontal and vertical padding within a window frame.
|
/// Horizontal and vertical padding within a window frame.
|
||||||
|
@ -25,13 +26,24 @@ pub struct Style {
|
||||||
/// The text starts after this many pixels.
|
/// The text starts after this many pixels.
|
||||||
pub start_icon_width: f32,
|
pub start_icon_width: f32,
|
||||||
|
|
||||||
pub interact: Interact,
|
|
||||||
|
|
||||||
// -----------------------------------------------
|
// -----------------------------------------------
|
||||||
// Purely visual:
|
// Purely visual:
|
||||||
|
pub interact: Interact,
|
||||||
|
|
||||||
|
// TODO: an WidgetStyle ?
|
||||||
|
pub text_color: Color,
|
||||||
|
|
||||||
/// For stuff like check marks in check boxes.
|
/// For stuff like check marks in check boxes.
|
||||||
pub line_width: f32,
|
pub line_width: f32,
|
||||||
|
|
||||||
|
pub thin_outline: Outline,
|
||||||
|
|
||||||
|
/// e.g. the background of windows
|
||||||
|
pub background_fill_color: Color,
|
||||||
|
|
||||||
|
/// e.g. the background of the slider or text edit
|
||||||
|
pub dark_bg_color: Color,
|
||||||
|
|
||||||
pub cursor_blink_hz: f32,
|
pub cursor_blink_hz: f32,
|
||||||
pub text_cursor_width: f32,
|
pub text_cursor_width: f32,
|
||||||
|
|
||||||
|
@ -59,12 +71,16 @@ impl Default for Style {
|
||||||
item_spacing: vec2(8.0, 4.0),
|
item_spacing: vec2(8.0, 4.0),
|
||||||
indent: 21.0,
|
indent: 21.0,
|
||||||
clickable_diameter: 22.0,
|
clickable_diameter: 22.0,
|
||||||
start_icon_width: 16.0,
|
start_icon_width: 14.0,
|
||||||
interact: Default::default(),
|
interact: Default::default(),
|
||||||
|
text_color: gray(160, 255),
|
||||||
line_width: 1.0,
|
line_width: 1.0,
|
||||||
|
thin_outline: Outline::new(0.5, GRAY),
|
||||||
|
background_fill_color: gray(32, 250),
|
||||||
|
dark_bg_color: gray(0, 140),
|
||||||
cursor_blink_hz: 1.0,
|
cursor_blink_hz: 1.0,
|
||||||
text_cursor_width: 2.0,
|
text_cursor_width: 2.0,
|
||||||
animation_time: 1.0 / 20.0,
|
animation_time: 1.0 / 15.0,
|
||||||
window: Window::default(),
|
window: Window::default(),
|
||||||
menu_bar: MenuBar::default(),
|
menu_bar: MenuBar::default(),
|
||||||
clip_rect_margin: 3.0,
|
clip_rect_margin: 3.0,
|
||||||
|
@ -84,24 +100,58 @@ impl Default for Interact {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
active: WidgetStyle {
|
active: WidgetStyle {
|
||||||
fill_color: Some(srgba(120, 120, 200, 255)),
|
bg_fill_color: None,
|
||||||
|
fill_color: srgba(120, 120, 200, 255),
|
||||||
stroke_color: WHITE,
|
stroke_color: WHITE,
|
||||||
stroke_width: 2.0,
|
stroke_width: 2.0,
|
||||||
outline: Some(Outline::new(2.0, WHITE)),
|
rect_outline: Some(Outline::new(1.0, WHITE)),
|
||||||
corner_radius: 5.0,
|
corner_radius: 5.0,
|
||||||
},
|
},
|
||||||
hovered: WidgetStyle {
|
hovered: WidgetStyle {
|
||||||
fill_color: Some(srgba(100, 100, 150, 255)),
|
bg_fill_color: None,
|
||||||
|
fill_color: srgba(100, 100, 150, 255),
|
||||||
stroke_color: WHITE,
|
stroke_color: WHITE,
|
||||||
stroke_width: 1.5,
|
stroke_width: 1.5,
|
||||||
outline: None,
|
rect_outline: Some(Outline::new(1.0, WHITE)),
|
||||||
corner_radius: 5.0,
|
corner_radius: 5.0,
|
||||||
},
|
},
|
||||||
inactive: WidgetStyle {
|
inactive: WidgetStyle {
|
||||||
fill_color: Some(srgba(60, 60, 80, 255)),
|
bg_fill_color: None,
|
||||||
|
fill_color: srgba(60, 60, 80, 255),
|
||||||
|
stroke_color: gray(210, 255), // Mustn't look grayed out!
|
||||||
|
stroke_width: 1.0,
|
||||||
|
rect_outline: Some(Outline::new(0.5, WHITE)),
|
||||||
|
corner_radius: 0.0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Interact {
|
||||||
|
pub fn classic() -> Self {
|
||||||
|
Self {
|
||||||
|
active: WidgetStyle {
|
||||||
|
bg_fill_color: Some(srgba(120, 120, 200, 255)),
|
||||||
|
fill_color: srgba(120, 120, 200, 255),
|
||||||
|
stroke_color: WHITE,
|
||||||
|
stroke_width: 2.0,
|
||||||
|
rect_outline: Some(Outline::new(2.0, WHITE)),
|
||||||
|
corner_radius: 5.0,
|
||||||
|
},
|
||||||
|
hovered: WidgetStyle {
|
||||||
|
bg_fill_color: Some(srgba(100, 100, 150, 255)),
|
||||||
|
fill_color: srgba(100, 100, 150, 255),
|
||||||
|
stroke_color: WHITE,
|
||||||
|
stroke_width: 1.5,
|
||||||
|
rect_outline: None,
|
||||||
|
corner_radius: 5.0,
|
||||||
|
},
|
||||||
|
inactive: WidgetStyle {
|
||||||
|
bg_fill_color: Some(srgba(60, 60, 80, 255)),
|
||||||
|
fill_color: srgba(60, 60, 80, 255),
|
||||||
stroke_color: gray(220, 255), // Mustn't look grayed out!
|
stroke_color: gray(220, 255), // Mustn't look grayed out!
|
||||||
stroke_width: 1.0,
|
stroke_width: 1.0,
|
||||||
outline: None,
|
rect_outline: None,
|
||||||
corner_radius: 0.0,
|
corner_radius: 0.0,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -122,8 +172,12 @@ impl Interact {
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
|
||||||
pub struct WidgetStyle {
|
pub struct WidgetStyle {
|
||||||
/// Fill color of the interactive part of a component (button, slider grab, checkbox, ...)
|
/// Background color of widget
|
||||||
pub fill_color: Option<Color>,
|
pub bg_fill_color: Option<Color>,
|
||||||
|
|
||||||
|
/// Fill color of the interactive part of a component (slider grab, checkbox, ...)
|
||||||
|
/// When you need a fill_color.
|
||||||
|
pub fill_color: Color,
|
||||||
|
|
||||||
/// Stroke and text color of the interactive part of a component (button, slider grab, checkbox, ...)
|
/// Stroke and text color of the interactive part of a component (button, slider grab, checkbox, ...)
|
||||||
pub stroke_color: Color,
|
pub stroke_color: Color,
|
||||||
|
@ -131,8 +185,9 @@ pub struct WidgetStyle {
|
||||||
/// For lines etc
|
/// For lines etc
|
||||||
pub stroke_width: f32,
|
pub stroke_width: f32,
|
||||||
|
|
||||||
/// For rectangles
|
/// For surrounding rectangle of things that need it,
|
||||||
pub outline: Option<Outline>,
|
/// like buttons, the box of the checkbox, etc.
|
||||||
|
pub rect_outline: Option<Outline>,
|
||||||
|
|
||||||
/// Button frames etdc
|
/// Button frames etdc
|
||||||
pub corner_radius: f32,
|
pub corner_radius: f32,
|
||||||
|
@ -163,15 +218,6 @@ impl Default for MenuBar {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Style {
|
impl Style {
|
||||||
/// e.g. the background of the slider
|
|
||||||
pub fn background_fill_color(&self) -> Color {
|
|
||||||
gray(34, 250)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn text_color(&self) -> Color {
|
|
||||||
gray(255, 200)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Use this style for interactive things
|
/// Use this style for interactive things
|
||||||
pub fn interact(&self, interact: &InteractInfo) -> &WidgetStyle {
|
pub fn interact(&self, interact: &InteractInfo) -> &WidgetStyle {
|
||||||
self.interact.style(interact)
|
self.interact.style(interact)
|
||||||
|
@ -185,7 +231,9 @@ impl Style {
|
||||||
vec2(box_side, box_side),
|
vec2(box_side, box_side),
|
||||||
);
|
);
|
||||||
|
|
||||||
let small_icon_rect = Rect::from_center_size(big_icon_rect.center(), vec2(10.0, 10.0));
|
let small_rect_side = 8.0; // TODO: make a parameter
|
||||||
|
let small_icon_rect =
|
||||||
|
Rect::from_center_size(big_icon_rect.center(), Vec2::splat(small_rect_side));
|
||||||
|
|
||||||
(small_icon_rect, big_icon_rect)
|
(small_icon_rect, big_icon_rect)
|
||||||
}
|
}
|
||||||
|
|
|
@ -135,6 +135,7 @@ pub enum PaintCmd {
|
||||||
color: Color,
|
color: Color,
|
||||||
width: f32,
|
width: f32,
|
||||||
},
|
},
|
||||||
|
// TODO: remove. Just have Path.
|
||||||
LinePath {
|
LinePath {
|
||||||
points: Vec<Pos2>,
|
points: Vec<Pos2>,
|
||||||
color: Color,
|
color: Color,
|
||||||
|
|
|
@ -473,7 +473,7 @@ impl Ui {
|
||||||
text_style: TextStyle,
|
text_style: TextStyle,
|
||||||
color: Option<Color>,
|
color: Option<Color>,
|
||||||
) {
|
) {
|
||||||
let color = color.unwrap_or_else(|| self.style().text_color());
|
let color = color.unwrap_or_else(|| self.style().text_color);
|
||||||
self.add_paint_cmd(PaintCmd::Text {
|
self.add_paint_cmd(PaintCmd::Text {
|
||||||
pos,
|
pos,
|
||||||
galley,
|
galley,
|
||||||
|
|
|
@ -226,13 +226,12 @@ impl Widget for Button {
|
||||||
let mut size = galley.size + 2.0 * padding;
|
let mut size = galley.size + 2.0 * padding;
|
||||||
size.y = size.y.max(ui.style().clickable_diameter);
|
size.y = size.y.max(ui.style().clickable_diameter);
|
||||||
let interact = ui.reserve_space(size, Some(id));
|
let interact = ui.reserve_space(size, Some(id));
|
||||||
let mut text_cursor = interact.rect.left_center() + vec2(padding.x, -0.5 * galley.size.y);
|
let text_cursor = interact.rect.left_center() + vec2(padding.x, -0.5 * galley.size.y);
|
||||||
text_cursor.y += 2.0; // TODO: why is this needed?
|
let bg_fill_color = fill_color.or(ui.style().interact(&interact).bg_fill_color);
|
||||||
let fill_color = fill_color.or(ui.style().interact(&interact).fill_color);
|
|
||||||
ui.add_paint_cmd(PaintCmd::Rect {
|
ui.add_paint_cmd(PaintCmd::Rect {
|
||||||
corner_radius: ui.style().interact(&interact).corner_radius,
|
corner_radius: ui.style().interact(&interact).corner_radius,
|
||||||
fill_color: fill_color,
|
fill_color: bg_fill_color,
|
||||||
outline: ui.style().interact(&interact).outline,
|
outline: ui.style().interact(&interact).rect_outline,
|
||||||
rect: interact.rect,
|
rect: interact.rect,
|
||||||
});
|
});
|
||||||
let stroke_color = ui.style().interact(&interact).stroke_color;
|
let stroke_color = ui.style().interact(&interact).stroke_color;
|
||||||
|
@ -286,9 +285,9 @@ impl<'a> Widget for Checkbox<'a> {
|
||||||
}
|
}
|
||||||
let (small_icon_rect, big_icon_rect) = ui.style().icon_rectangles(interact.rect);
|
let (small_icon_rect, big_icon_rect) = ui.style().icon_rectangles(interact.rect);
|
||||||
ui.add_paint_cmd(PaintCmd::Rect {
|
ui.add_paint_cmd(PaintCmd::Rect {
|
||||||
corner_radius: 3.0,
|
corner_radius: ui.style().interact(&interact).corner_radius,
|
||||||
fill_color: ui.style().interact(&interact).fill_color,
|
fill_color: ui.style().interact(&interact).bg_fill_color,
|
||||||
outline: None,
|
outline: ui.style().interact(&interact).rect_outline,
|
||||||
rect: big_icon_rect,
|
rect: big_icon_rect,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -356,15 +355,15 @@ impl Widget for RadioButton {
|
||||||
let text_cursor =
|
let text_cursor =
|
||||||
interact.rect.min + ui.style().button_padding + vec2(ui.style().start_icon_width, 0.0);
|
interact.rect.min + ui.style().button_padding + vec2(ui.style().start_icon_width, 0.0);
|
||||||
|
|
||||||
let fill_color = ui.style().interact(&interact).fill_color;
|
let bg_fill_color = ui.style().interact(&interact).bg_fill_color;
|
||||||
let stroke_color = ui.style().interact(&interact).stroke_color;
|
let stroke_color = ui.style().interact(&interact).stroke_color;
|
||||||
|
|
||||||
let (small_icon_rect, big_icon_rect) = ui.style().icon_rectangles(interact.rect);
|
let (small_icon_rect, big_icon_rect) = ui.style().icon_rectangles(interact.rect);
|
||||||
|
|
||||||
ui.add_paint_cmd(PaintCmd::Circle {
|
ui.add_paint_cmd(PaintCmd::Circle {
|
||||||
center: big_icon_rect.center(),
|
center: big_icon_rect.center(),
|
||||||
fill_color,
|
fill_color: bg_fill_color,
|
||||||
outline: None,
|
outline: ui.style().interact(&interact).rect_outline, // TODO
|
||||||
radius: big_icon_rect.width() / 2.0,
|
radius: big_icon_rect.width() / 2.0,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -373,7 +372,7 @@ impl Widget for RadioButton {
|
||||||
center: small_icon_rect.center(),
|
center: small_icon_rect.center(),
|
||||||
fill_color: Some(stroke_color),
|
fill_color: Some(stroke_color),
|
||||||
outline: None,
|
outline: None,
|
||||||
radius: small_icon_rect.width() / 2.0,
|
radius: small_icon_rect.width() / 3.0,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -386,7 +385,7 @@ impl Widget for RadioButton {
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
pub struct Separator {
|
pub struct Separator {
|
||||||
line_width: f32,
|
line_width: Option<f32>,
|
||||||
min_spacing: f32,
|
min_spacing: f32,
|
||||||
extra: f32,
|
extra: f32,
|
||||||
color: Color,
|
color: Color,
|
||||||
|
@ -395,7 +394,7 @@ pub struct Separator {
|
||||||
impl Separator {
|
impl Separator {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
line_width: 2.0,
|
line_width: None,
|
||||||
min_spacing: 6.0,
|
min_spacing: 6.0,
|
||||||
extra: 0.0,
|
extra: 0.0,
|
||||||
color: color::WHITE,
|
color: color::WHITE,
|
||||||
|
@ -403,7 +402,7 @@ impl Separator {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn line_width(mut self, line_width: f32) -> Self {
|
pub fn line_width(mut self, line_width: f32) -> Self {
|
||||||
self.line_width = line_width;
|
self.line_width = Some(line_width);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -426,26 +425,36 @@ impl Separator {
|
||||||
|
|
||||||
impl Widget for Separator {
|
impl Widget for Separator {
|
||||||
fn ui(self, ui: &mut Ui) -> GuiResponse {
|
fn ui(self, ui: &mut Ui) -> GuiResponse {
|
||||||
|
let Separator {
|
||||||
|
line_width,
|
||||||
|
min_spacing,
|
||||||
|
extra,
|
||||||
|
color,
|
||||||
|
} = self;
|
||||||
|
|
||||||
|
let line_width = line_width.unwrap_or_else(|| ui.style().line_width);
|
||||||
|
|
||||||
let available_space = ui.available_finite().size();
|
let available_space = ui.available_finite().size();
|
||||||
|
|
||||||
let extra = self.extra;
|
|
||||||
let (points, interact) = match ui.layout().dir() {
|
let (points, interact) = match ui.layout().dir() {
|
||||||
Direction::Horizontal => {
|
Direction::Horizontal => {
|
||||||
let interact = ui.reserve_space(vec2(self.min_spacing, available_space.y), None);
|
let interact = ui.reserve_space(vec2(min_spacing, available_space.y), None);
|
||||||
|
let r = &interact.rect;
|
||||||
(
|
(
|
||||||
[
|
[
|
||||||
pos2(interact.rect.center().x, interact.rect.top() - extra),
|
pos2(r.center().x, r.top() - extra),
|
||||||
pos2(interact.rect.center().x, interact.rect.bottom() + extra),
|
pos2(r.center().x, r.bottom() + extra),
|
||||||
],
|
],
|
||||||
interact,
|
interact,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Direction::Vertical => {
|
Direction::Vertical => {
|
||||||
let interact = ui.reserve_space(vec2(available_space.x, self.min_spacing), None);
|
let interact = ui.reserve_space(vec2(available_space.x, min_spacing), None);
|
||||||
|
let r = &interact.rect;
|
||||||
(
|
(
|
||||||
[
|
[
|
||||||
pos2(interact.rect.left() - extra, interact.rect.center().y),
|
pos2(r.left() - extra, r.center().y),
|
||||||
pos2(interact.rect.right() + extra, interact.rect.center().y),
|
pos2(r.right() + extra, r.center().y),
|
||||||
],
|
],
|
||||||
interact,
|
interact,
|
||||||
)
|
)
|
||||||
|
@ -453,8 +462,8 @@ impl Widget for Separator {
|
||||||
};
|
};
|
||||||
ui.add_paint_cmd(PaintCmd::LineSegment {
|
ui.add_paint_cmd(PaintCmd::LineSegment {
|
||||||
points,
|
points,
|
||||||
color: self.color,
|
color: color,
|
||||||
width: self.line_width,
|
width: line_width,
|
||||||
});
|
});
|
||||||
ui.response(interact)
|
ui.response(interact)
|
||||||
}
|
}
|
||||||
|
|
|
@ -176,14 +176,14 @@ impl<'a> Widget for Slider<'a> {
|
||||||
ui.add_paint_cmd(PaintCmd::Rect {
|
ui.add_paint_cmd(PaintCmd::Rect {
|
||||||
rect: rail_rect,
|
rect: rail_rect,
|
||||||
corner_radius: rail_radius,
|
corner_radius: rail_radius,
|
||||||
fill_color: Some(ui.style().background_fill_color()),
|
fill_color: Some(ui.style().background_fill_color),
|
||||||
outline: Some(Outline::new(1.0, color::gray(200, 255))), // TODO
|
outline: Some(Outline::new(1.0, color::gray(200, 255))), // TODO
|
||||||
});
|
});
|
||||||
|
|
||||||
ui.add_paint_cmd(PaintCmd::Circle {
|
ui.add_paint_cmd(PaintCmd::Circle {
|
||||||
center: pos2(marker_center_x, rail_rect.center().y),
|
center: pos2(marker_center_x, rail_rect.center().y),
|
||||||
radius: handle_radius,
|
radius: handle_radius,
|
||||||
fill_color: ui.style().interact(&interact).fill_color,
|
fill_color: Some(ui.style().interact(&interact).fill_color),
|
||||||
outline: Some(Outline::new(
|
outline: Some(Outline::new(
|
||||||
ui.style().interact(&interact).stroke_width,
|
ui.style().interact(&interact).stroke_width,
|
||||||
ui.style().interact(&interact).stroke_color,
|
ui.style().interact(&interact).stroke_color,
|
||||||
|
|
|
@ -13,6 +13,7 @@ pub struct TextEdit<'t> {
|
||||||
id: Option<Id>,
|
id: Option<Id>,
|
||||||
text_style: TextStyle, // TODO: Option<TextStyle>, where None means "use the default for the current Ui"
|
text_style: TextStyle, // TODO: Option<TextStyle>, where None means "use the default for the current Ui"
|
||||||
text_color: Option<Color>,
|
text_color: Option<Color>,
|
||||||
|
multiline: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'t> TextEdit<'t> {
|
impl<'t> TextEdit<'t> {
|
||||||
|
@ -22,6 +23,7 @@ impl<'t> TextEdit<'t> {
|
||||||
id: None,
|
id: None,
|
||||||
text_style: TextStyle::Body,
|
text_style: TextStyle::Body,
|
||||||
text_color: Default::default(),
|
text_color: Default::default(),
|
||||||
|
multiline: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,6 +41,11 @@ impl<'t> TextEdit<'t> {
|
||||||
self.text_color = Some(text_color);
|
self.text_color = Some(text_color);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn multiline(mut self, multiline: bool) -> Self {
|
||||||
|
self.multiline = multiline;
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'t> Widget for TextEdit<'t> {
|
impl<'t> Widget for TextEdit<'t> {
|
||||||
|
@ -48,6 +55,7 @@ impl<'t> Widget for TextEdit<'t> {
|
||||||
id,
|
id,
|
||||||
text_style,
|
text_style,
|
||||||
text_color,
|
text_color,
|
||||||
|
multiline,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
let id = ui.make_child_id(id);
|
let id = ui.make_child_id(id);
|
||||||
|
@ -57,7 +65,11 @@ impl<'t> Widget for TextEdit<'t> {
|
||||||
let font = &ui.fonts()[text_style];
|
let font = &ui.fonts()[text_style];
|
||||||
let line_spacing = font.line_spacing();
|
let line_spacing = font.line_spacing();
|
||||||
let available_width = ui.available().width();
|
let available_width = ui.available().width();
|
||||||
let mut galley = font.layout_multiline(text.as_str(), available_width);
|
let mut galley = if multiline {
|
||||||
|
font.layout_multiline(text.as_str(), available_width)
|
||||||
|
} else {
|
||||||
|
font.layout_single_line(text.as_str())
|
||||||
|
};
|
||||||
let desired_size = galley.size.max(vec2(available_width, line_spacing));
|
let desired_size = galley.size.max(vec2(available_width, line_spacing));
|
||||||
let interact = ui.reserve_space(desired_size, Some(id));
|
let interact = ui.reserve_space(desired_size, Some(id));
|
||||||
|
|
||||||
|
@ -95,19 +107,24 @@ impl<'t> Widget for TextEdit<'t> {
|
||||||
|
|
||||||
// layout again to avoid frame delay:
|
// layout again to avoid frame delay:
|
||||||
let font = &ui.fonts()[text_style];
|
let font = &ui.fonts()[text_style];
|
||||||
galley = font.layout_multiline(text.as_str(), available_width);
|
galley = if multiline {
|
||||||
|
font.layout_multiline(text.as_str(), available_width)
|
||||||
|
} else {
|
||||||
|
font.layout_single_line(text.as_str())
|
||||||
|
};
|
||||||
|
|
||||||
// dbg!(&galley);
|
// dbg!(&galley);
|
||||||
}
|
}
|
||||||
|
|
||||||
ui.add_paint_cmd(PaintCmd::Rect {
|
{
|
||||||
rect: interact.rect,
|
let bg_rect = interact.rect.expand(2.0); // breathing room for content
|
||||||
corner_radius: 0.0,
|
ui.add_paint_cmd(PaintCmd::Rect {
|
||||||
// fill_color: Some(color::BLACK),
|
rect: bg_rect,
|
||||||
fill_color: ui.style().interact(&interact).fill_color,
|
corner_radius: ui.style().interact.style(&interact).corner_radius,
|
||||||
// fill_color: Some(ui.style().background_fill_color()),
|
fill_color: Some(ui.style().dark_bg_color),
|
||||||
outline: None, //Some(Outline::new(1.0, color::WHITE)),
|
outline: ui.style().interact.style(&interact).rect_outline,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if has_kb_focus {
|
if has_kb_focus {
|
||||||
let cursor_blink_hz = ui.style().cursor_blink_hz;
|
let cursor_blink_hz = ui.style().cursor_blink_hz;
|
||||||
|
|
Loading…
Reference in a new issue