diff --git a/egui/src/containers/collapsing_header.rs b/egui/src/containers/collapsing_header.rs index 8899e736..1a98dbc3 100644 --- a/egui/src/containers/collapsing_header.rs +++ b/egui/src/containers/collapsing_header.rs @@ -182,15 +182,15 @@ impl CollapsingHeader { let id = ui.make_unique_child_id_full(id_source, Some(title)); let available = ui.available_finite(); - let text_pos = available.min + vec2(ui.style().indent, 0.0); - let galley = label.layout_width(ui, available.width() - ui.style().indent); + let text_pos = available.min + vec2(ui.style().spacing.indent, 0.0); + let galley = label.layout_width(ui, available.width() - ui.style().spacing.indent); let text_max_x = text_pos.x + galley.size.x; let desired_width = text_max_x - available.left(); let desired_width = desired_width.max(available.width()); let size = vec2( desired_width, - galley.size.y + 2.0 * ui.style().button_padding.y, + galley.size.y + 2.0 * ui.style().spacing.button_padding.y, ); let rect = ui.allocate_space(size); @@ -205,9 +205,9 @@ impl CollapsingHeader { let bg_index = ui.painter().add(PaintCmd::Noop); { - let (mut icon_rect, _) = ui.style().icon_rectangles(response.rect); + let (mut icon_rect, _) = ui.style().spacing.icon_rectangles(response.rect); icon_rect.set_center(pos2( - response.rect.left() + ui.style().indent / 2.0, + response.rect.left() + ui.style().spacing.indent / 2.0, response.rect.center().y, )); let icon_response = Response { diff --git a/egui/src/containers/frame.rs b/egui/src/containers/frame.rs index 0cc49b64..41567fa0 100644 --- a/egui/src/containers/frame.rs +++ b/egui/src/containers/frame.rs @@ -15,10 +15,10 @@ pub struct Frame { impl Frame { pub fn window(style: &Style) -> Self { Self { - margin: style.window_padding, - corner_radius: style.window.corner_radius, - fill: Some(style.background_fill), - outline: style.interact.inactive.bg_outline, // because we can resize windows + margin: style.spacing.window_padding, + corner_radius: style.visuals.window_corner_radius, + fill: Some(style.visuals.background_fill), + outline: style.visuals.interacted.inactive.bg_outline, // because we can resize windows } } @@ -35,16 +35,16 @@ impl Frame { Self { margin: Vec2::splat(1.0), corner_radius: 2.0, - fill: Some(style.background_fill), + fill: Some(style.visuals.background_fill), outline: Some(LineStyle::new(1.0, Srgba::gray(128))), } } pub fn popup(style: &Style) -> Self { Self { - margin: style.window_padding, + margin: style.spacing.window_padding, corner_radius: 5.0, - fill: Some(style.background_fill), + fill: Some(style.visuals.background_fill), outline: Some(LineStyle::new(1.0, Srgba::gray(128))), } } diff --git a/egui/src/containers/resize.rs b/egui/src/containers/resize.rs index 7fda9824..cd90f80d 100644 --- a/egui/src/containers/resize.rs +++ b/egui/src/containers/resize.rs @@ -145,7 +145,7 @@ impl Resize { let corner_response = if self.resizable { // Resize-corner: - let corner_size = Vec2::splat(ui.style().resize_corner_size); + let corner_size = Vec2::splat(ui.style().visuals.resize_corner_size); let corner_rect = Rect::from_min_size(position + state.desired_size - corner_size, corner_size); let corner_response = ui.interact(corner_rect, id.with("corner"), Sense::drag()); @@ -169,7 +169,7 @@ impl Resize { let inner_rect = Rect::from_min_size(position, state.desired_size); - let mut content_clip_rect = inner_rect.expand(ui.style().clip_rect_margin); + let mut content_clip_rect = inner_rect.expand(ui.style().visuals.clip_rect_margin); // If we pull the resize handle to shrink, we want to TRY to shrink it. // After laying out the contents, we might be much bigger. @@ -177,7 +177,9 @@ impl Resize { // then we will clip the contents of the region even thought the result gets larger. This is simply ugly! // So we use the memory of last_content_size to make the clip rect large enough. content_clip_rect.max = content_clip_rect.max.max( - inner_rect.min + state.last_content_size + Vec2::splat(ui.style().clip_rect_margin), + inner_rect.min + + state.last_content_size + + Vec2::splat(ui.style().visuals.clip_rect_margin), ); content_clip_rect = content_clip_rect.intersect(ui.clip_rect()); // Respect parent region @@ -236,7 +238,7 @@ impl Resize { rect, corner_radius: 3.0, fill: None, - outline: Some(ui.style().thin_outline), + outline: Some(ui.style().visuals.thin_outline), }); } @@ -250,7 +252,7 @@ impl Resize { ui.memory().resize.insert(id, state); - if ui.ctx().style().debug_resize { + if ui.ctx().style().visuals.debug_resize { ui.ctx().debug_painter().debug_rect( Rect::from_min_size(content_ui.top_left(), state.desired_size), color::GREEN, diff --git a/egui/src/containers/scroll_area.rs b/egui/src/containers/scroll_area.rs index a33271c8..b7f34a5a 100644 --- a/egui/src/containers/scroll_area.rs +++ b/egui/src/containers/scroll_area.rs @@ -99,7 +99,7 @@ impl ScrollArea { inner_rect.min - state.offset, vec2(inner_size.x, f32::INFINITY), )); - let mut content_clip_rect = inner_rect.expand(ui.style().clip_rect_margin); + let mut content_clip_rect = inner_rect.expand(ui.style().visuals.clip_rect_margin); content_clip_rect = content_clip_rect.intersect(ui.clip_rect()); content_clip_rect.max.x = ui.clip_rect().max.x - current_scroll_bar_width; // Nice handling of forced resizing beyond the possible content_ui.set_clip_rect(content_clip_rect); @@ -175,7 +175,7 @@ impl Prepared { if current_scroll_bar_width > 0.0 { let animation_t = current_scroll_bar_width / max_scroll_bar_width; // margin between contents and scroll bar - let margin = animation_t * ui.style().item_spacing.x; + let margin = animation_t * ui.style().spacing.item_spacing.x; let left = inner_rect.right() + margin; let right = outer_rect.right(); let corner_radius = (right - left) / 2.0; @@ -243,7 +243,7 @@ impl Prepared { ui.painter().add(paint::PaintCmd::Rect { rect: outer_scroll_rect, corner_radius, - fill: Some(ui.style().dark_bg_color), + fill: Some(ui.style().visuals.dark_bg_color), outline: None, }); @@ -274,5 +274,5 @@ impl Prepared { } fn max_scroll_bar_width_with_margin(ui: &Ui) -> f32 { - ui.style().item_spacing.x + 16.0 + ui.style().spacing.item_spacing.x + 16.0 } diff --git a/egui/src/containers/window.rs b/egui/src/containers/window.rs index 0c11324d..4eaa91d2 100644 --- a/egui/src/containers/window.rs +++ b/egui/src/containers/window.rs @@ -202,7 +202,7 @@ impl<'open> Window<'open> { let last_frame_outer_rect = area.state().rect(); let interaction = if possible.movable || possible.resizable { let title_bar_height = - title_label.font_height(ctx.fonts()) + 1.0 * ctx.style().item_spacing.y; // this could be better + title_label.font_height(ctx.fonts()) + 1.0 * ctx.style().spacing.item_spacing.y; // this could be better let margins = 2.0 * frame.margin + vec2(0.0, title_bar_height); window_interaction( @@ -255,7 +255,7 @@ impl<'open> Window<'open> { .add_contents(&mut frame.content_ui, collapsing_id, |ui| { resize.show(ui, |ui| { // Add some spacing between title and content: - ui.allocate_space(ui.style().item_spacing); + ui.allocate_space(ui.style().spacing.item_spacing); if let Some(scroll) = scroll { scroll.show(ui, add_contents) @@ -294,14 +294,14 @@ impl<'open> Window<'open> { &mut area_content_ui, outer_rect, interaction, - ctx.style().interact.active, + ctx.style().visuals.interacted.active, ); } else if let Some(hover_interaction) = hover_interaction { paint_frame_interaction( &mut area_content_ui, outer_rect, hover_interaction, - ctx.style().interact.hovered, + ctx.style().visuals.interacted.hovered, ); } } @@ -312,7 +312,7 @@ impl<'open> Window<'open> { } fn paint_resize_corner(ui: &mut Ui, outer_rect: Rect, frame_outline: Option) { - let corner_size = Vec2::splat(ui.style().resize_corner_size); + let corner_size = Vec2::splat(ui.style().visuals.resize_corner_size); let handle_offset = -Vec2::splat(2.0); let corner_rect = Rect::from_min_size(outer_rect.max - corner_size + handle_offset, corner_size); @@ -468,32 +468,32 @@ fn resize_hover( return None; } - let side_interact_radius = ctx.style().resize_interact_radius_side; - let corner_interact_radius = ctx.style().resize_interact_radius_corner; - if rect.expand(side_interact_radius).contains(mouse_pos) { + let side_grab_radius = ctx.style().interaction.resize_grab_radius_side; + let corner_grab_radius = ctx.style().interaction.resize_grab_radius_corner; + if rect.expand(side_grab_radius).contains(mouse_pos) { let (mut left, mut right, mut top, mut bottom) = Default::default(); if possible.resizable { - right = (rect.right() - mouse_pos.x).abs() <= side_interact_radius; - bottom = (rect.bottom() - mouse_pos.y).abs() <= side_interact_radius; + right = (rect.right() - mouse_pos.x).abs() <= side_grab_radius; + bottom = (rect.bottom() - mouse_pos.y).abs() <= side_grab_radius; - if rect.right_bottom().distance(mouse_pos) < corner_interact_radius { + if rect.right_bottom().distance(mouse_pos) < corner_grab_radius { right = true; bottom = true; } if possible.movable { - left = (rect.left() - mouse_pos.x).abs() <= side_interact_radius; - top = (rect.top() - mouse_pos.y).abs() <= side_interact_radius; + left = (rect.left() - mouse_pos.x).abs() <= side_grab_radius; + top = (rect.top() - mouse_pos.y).abs() <= side_grab_radius; - if rect.right_top().distance(mouse_pos) < corner_interact_radius { + if rect.right_top().distance(mouse_pos) < corner_grab_radius { right = true; top = true; } - if rect.left_top().distance(mouse_pos) < corner_interact_radius { + if rect.left_top().distance(mouse_pos) < corner_grab_radius { left = true; top = true; } - if rect.left_bottom().distance(mouse_pos) < corner_interact_radius { + if rect.left_bottom().distance(mouse_pos) < corner_grab_radius { left = true; bottom = true; } @@ -530,9 +530,9 @@ fn paint_frame_interaction( ui: &mut Ui, rect: Rect, interaction: WindowInteraction, - style: style::WidgetStyle, + visuals: style::WidgetVisuals, ) { - let cr = ui.style().window.corner_radius; + let cr = ui.style().visuals.window_corner_radius; let Rect { min, max } = rect; let mut path = Path::default(); @@ -567,7 +567,7 @@ fn paint_frame_interaction( path, closed: false, fill: None, - outline: style.bg_outline, + outline: visuals.bg_outline, }); } @@ -591,8 +591,8 @@ fn show_title_bar( let title_bar_and_rect = ui.horizontal_centered(|ui| { ui.set_desired_height(title_label.font_height(ui.fonts())); - let item_spacing = ui.style().item_spacing; - let button_size = ui.style().start_icon_width; + let item_spacing = ui.style().spacing.item_spacing; + let button_size = ui.style().spacing.icon_width; if collapsible { // TODO: make clickable radius larger @@ -668,10 +668,10 @@ impl TitleBar { // paint separator between title and content: let left = outer_rect.left(); let right = outer_rect.right(); - let y = content_rect.top() + ui.style().item_spacing.y * 0.5; + let y = content_rect.top() + ui.style().spacing.item_spacing.y * 0.5; ui.painter().line_segment( [pos2(left, y), pos2(right, y)], - ui.style().interact.inactive.bg_outline.unwrap(), + ui.style().visuals.interacted.inactive.bg_outline.unwrap(), ); } @@ -686,10 +686,10 @@ impl TitleBar { } fn close_button_ui(&self, ui: &mut Ui) -> Response { - let button_size = ui.style().start_icon_width; + let button_size = ui.style().spacing.icon_width; let button_rect = Rect::from_min_size( pos2( - self.rect.right() - ui.style().item_spacing.x - button_size, + self.rect.right() - ui.style().spacing.item_spacing.x - button_size, self.rect.center().y - 0.5 * button_size, ), Vec2::splat(button_size), diff --git a/egui/src/context.rs b/egui/src/context.rs index 534e1dc0..3a30c836 100644 --- a/egui/src/context.rs +++ b/egui/src/context.rs @@ -337,8 +337,8 @@ impl Context { // --------------------------------------------------------------------- pub fn layer_at(&self, pos: Pos2) -> Option { - let resize_interact_radius_side = self.style().resize_interact_radius_side; - self.memory().layer_at(pos, resize_interact_radius_side) + let resize_grab_radius_side = self.style().interaction.resize_grab_radius_side; + self.memory().layer_at(pos, resize_grab_radius_side) } pub fn contains_mouse(&self, layer: Layer, clip_rect: Rect, rect: Rect) -> bool { @@ -359,7 +359,7 @@ impl Context { interaction_id: Option, sense: Sense, ) -> Response { - let interact_rect = rect.expand2(0.5 * self.style().item_spacing); // make it easier to click. TODO: nice way to do this + let interact_rect = rect.expand2(0.5 * self.style().spacing.item_spacing); // make it easier to click. TODO: nice way to do this let hovered = self.contains_mouse(layer, clip_rect, interact_rect); let has_kb_focus = interaction_id .map(|id| self.memory().has_kb_focus(id)) diff --git a/egui/src/demos/app.rs b/egui/src/demos/app.rs index d1d5aafd..78c06dbb 100644 --- a/egui/src/demos/app.rs +++ b/egui/src/demos/app.rs @@ -473,7 +473,7 @@ impl Default for Widgets { impl Widgets { pub fn ui(&mut self, ui: &mut Ui) { ui.horizontal(|ui| { - ui.style_mut().item_spacing.x = 0.0; + ui.style_mut().spacing.item_spacing.x = 0.0; ui.add(label!("Text can have ").text_color(srgba(110, 255, 110, 255))); ui.add(label!("color ").text_color(srgba(128, 140, 255, 255))); ui.add(label!("and tooltips")).tooltip_text( @@ -520,7 +520,7 @@ impl Widgets { { ui.label("An angle stored as radians, but edited in degrees:"); ui.horizontal_centered(|ui| { - ui.style_mut().item_spacing.x = 0.0; + ui.style_mut().spacing.item_spacing.x = 0.0; ui.drag_angle(&mut self.angle); ui.label(format!(" = {} radians", self.angle)); }); diff --git a/egui/src/demos/toggle_switch.rs b/egui/src/demos/toggle_switch.rs index 106ee1a8..65b0fc47 100644 --- a/egui/src/demos/toggle_switch.rs +++ b/egui/src/demos/toggle_switch.rs @@ -4,7 +4,7 @@ use crate::{paint::PaintCmd, *}; // iOS style toggle switch pub fn toggle(ui: &mut Ui, on: &mut bool) -> Response { // First we must reserve some space to use: - let desired_size = vec2(2.0, 1.0) * ui.style().clickable_diameter; + let desired_size = vec2(2.0, 1.0) * ui.style().spacing.clickable_diameter; let rect = ui.allocate_space(desired_size); // Now that we have an area, we want to check for clicks. diff --git a/egui/src/layout.rs b/egui/src/layout.rs index beb8582f..309fd297 100644 --- a/egui/src/layout.rs +++ b/egui/src/layout.rs @@ -184,7 +184,7 @@ impl Layout { } cursor_change.x += child_size.x; - cursor_change.x += style.item_spacing.x; // Where to put next thing, if there is a next thing + cursor_change.x += style.spacing.item_spacing.x; // Where to put next thing, if there is a next thing } else { if let Some(align) = self.align { child_move.x += match align { @@ -197,7 +197,7 @@ impl Layout { child_size.x = child_size.x.max(available_size.x); }; cursor_change.y += child_size.y; - cursor_change.y += style.item_spacing.y; // Where to put next thing, if there is a next thing + cursor_change.y += style.spacing.item_spacing.y; // Where to put next thing, if there is a next thing } if self.is_reversed() { diff --git a/egui/src/menu.rs b/egui/src/menu.rs index 93a70338..96b4ad54 100644 --- a/egui/src/menu.rs +++ b/egui/src/menu.rs @@ -61,17 +61,17 @@ pub fn bar(ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) -> (R, Rect) ui.horizontal_centered(|ui| { Frame::menu_bar(ui.style()).show(ui, |ui| { let mut style = ui.style().clone(); - style.button_padding = vec2(2.0, 0.0); - // style.interact.active.bg_fill = None; - style.interact.active.bg_outline = None; - // style.interact.hovered.bg_fill = None; - style.interact.hovered.bg_outline = None; - style.interact.inactive.bg_fill = None; - style.interact.inactive.bg_outline = None; + style.spacing.button_padding = vec2(2.0, 0.0); + // style.visuals.interacted.active.bg_fill = None; + style.visuals.interacted.active.bg_outline = None; + // style.visuals.interacted.hovered.bg_fill = None; + style.visuals.interacted.hovered.bg_outline = None; + style.visuals.interacted.inactive.bg_fill = None; + style.visuals.interacted.inactive.bg_outline = None; ui.set_style(style); // Take full width and fixed height: - let height = ui.style().menu_bar.height; + let height = ui.style().spacing.menu_bar_height; ui.set_desired_height(height); ui.expand_to_size(vec2(ui.available().width(), height)); @@ -108,7 +108,7 @@ fn menu_impl<'c>( let mut button = Button::new(title); if bar_state.open_menu == Some(menu_id) { - button = button.fill(Some(ui.style().interact.active.main_fill)); + button = button.fill(Some(ui.style().visuals.interacted.active.main_fill)); } let button_response = ui.add(button); @@ -127,13 +127,13 @@ fn menu_impl<'c>( frame.show(ui, |ui| { resize.show(ui, |ui| { let mut style = ui.style().clone(); - style.button_padding = vec2(2.0, 0.0); - // style.interact.active.bg_fill = None; - style.interact.active.bg_outline = None; - // style.interact.hovered.bg_fill = None; - style.interact.hovered.bg_outline = None; - style.interact.inactive.bg_fill = None; - style.interact.inactive.bg_outline = None; + style.spacing.button_padding = vec2(2.0, 0.0); + // style.visuals.interacted.active.bg_fill = None; + style.visuals.interacted.active.bg_outline = None; + // style.visuals.interacted.hovered.bg_fill = None; + style.visuals.interacted.hovered.bg_outline = None; + style.visuals.interacted.inactive.bg_fill = None; + style.visuals.interacted.inactive.bg_outline = None; ui.set_style(style); ui.set_layout(Layout::justified(Direction::Vertical)); add_contents(ui) diff --git a/egui/src/style.rs b/egui/src/style.rs index b9a40052..b1af7f28 100644 --- a/egui/src/style.rs +++ b/egui/src/style.rs @@ -2,43 +2,86 @@ use crate::{color::*, math::*, paint::LineStyle, types::*}; -// TODO: split into Spacing and Style? /// Specifies the look and feel of a `Ui`. #[derive(Clone, Debug)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct Style { + pub spacing: Spacing, + pub interaction: Interaction, + pub visuals: Visuals, + + /// How many seconds a typical animation should last + pub animation_time: f32, +} + +impl Style { + // TODO: rename style.interact() to maybe... `style.response_visuals` ? + /// Use this style for interactive things + pub fn interact(&self, response: &Response) -> &WidgetVisuals { + self.visuals.interacted.style(response) + } +} + +#[derive(Clone, Debug)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +pub struct Spacing { + /// Horizontal and vertical spacing between widgets + pub item_spacing: Vec2, + /// 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 collapsing regions etc by this much. pub indent: f32, /// Anything clickable is (at least) this wide. pub clickable_diameter: f32, + /// Total width of a slider + pub slider_width: f32, + /// Checkboxes, radio button and collapsing headers have an icon at the start. /// The text starts after this many pixels. - pub start_icon_width: f32, + pub icon_width: f32, + pub menu_bar_height: f32, +} + +impl Spacing { + /// Returns small icon rectangle and big icon rectangle + pub fn icon_rectangles(&self, rect: Rect) -> (Rect, Rect) { + let box_side = self.icon_width; + let big_icon_rect = Rect::from_center_size( + pos2(rect.left() + box_side / 2.0, rect.center().y), + vec2(box_side, box_side), + ); + + 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) + } +} + +#[derive(Clone, Debug)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +pub struct Interaction { /// Mouse must be the close to the side of a window to resize - pub resize_interact_radius_side: f32, + pub resize_grab_radius_side: f32, /// Mouse must be the close to the corner of a window to resize - pub resize_interact_radius_corner: f32, + pub resize_grab_radius_corner: f32, +} - pub resize_corner_size: f32, +#[derive(Clone, Debug)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +pub struct Visuals { + pub interacted: Interacted, - // ----------------------------------------------- - // Purely visual: - pub interact: Interact, - - // TODO: an WidgetStyle ? pub text_color: Srgba, /// For stuff like check marks in check boxes. @@ -52,18 +95,14 @@ pub struct Style { /// e.g. the background of the slider or text edit pub dark_bg_color: Srgba, + pub window_corner_radius: f32, + + pub resize_corner_size: f32, + /// Blink text cursor by this frequency. If None, always show the cursor. pub cursor_blink_hz: Option, pub text_cursor_width: f32, - // TODO: add ability to disable animations! - /// How many seconds a typical animation should last - pub animation_time: f32, - - pub window: Window, - - pub menu_bar: MenuBar, - /// Allow child widgets to be just on the border and still have an outline with some thickness pub clip_rect_margin: f32, @@ -73,86 +112,17 @@ pub struct Style { pub debug_resize: bool, } -impl Default for Style { - fn default() -> Self { - Self { - window_padding: vec2(6.0, 6.0), - button_padding: vec2(4.0, 1.0), - item_spacing: vec2(8.0, 4.0), - indent: 21.0, - clickable_diameter: 22.0, - start_icon_width: 14.0, - resize_interact_radius_side: 5.0, - resize_interact_radius_corner: 10.0, - resize_corner_size: 16.0, - interact: Default::default(), - text_color: Srgba::gray(160), - line_width: 1.0, - thin_outline: LineStyle::new(0.5, GRAY), - background_fill: Rgba::luminance_alpha(0.013, 0.95).into(), - dark_bg_color: Srgba::black_alpha(140), - cursor_blink_hz: None, // Some(1.0) - text_cursor_width: 2.0, - animation_time: 1.0 / 15.0, - window: Window::default(), - menu_bar: MenuBar::default(), - clip_rect_margin: 3.0, - debug_widget_rects: false, - debug_resize: false, - } - } -} - #[derive(Clone, Copy, Debug)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -pub struct Interact { - pub active: WidgetStyle, - pub hovered: WidgetStyle, - pub inactive: WidgetStyle, - pub disabled: WidgetStyle, +pub struct Interacted { + pub active: WidgetVisuals, + pub hovered: WidgetVisuals, + pub inactive: WidgetVisuals, + pub disabled: WidgetVisuals, } -impl Default for Interact { - fn default() -> Self { - Self { - active: WidgetStyle { - bg_fill: Some(Srgba::black_alpha(128)), - bg_outline: Some(LineStyle::new(2.0, WHITE)), - corner_radius: 0.0, - main_fill: srgba(120, 120, 200, 255), - stroke_color: WHITE, - stroke_width: 2.0, - }, - hovered: WidgetStyle { - bg_fill: None, - bg_outline: Some(LineStyle::new(1.0, WHITE)), - corner_radius: 2.0, - main_fill: srgba(100, 100, 150, 255), - stroke_color: Srgba::gray(240), - stroke_width: 1.5, - }, - inactive: WidgetStyle { - bg_fill: None, - bg_outline: Some(LineStyle::new(1.0, Srgba::gray(128))), - corner_radius: 4.0, - main_fill: srgba(60, 60, 80, 255), - stroke_color: Srgba::gray(200), // Mustn't look grayed out! - stroke_width: 1.0, - }, - disabled: WidgetStyle { - bg_fill: None, - bg_outline: Some(LineStyle::new(0.5, Srgba::gray(128))), - corner_radius: 4.0, - main_fill: srgba(50, 50, 50, 255), - stroke_color: Srgba::gray(128), // Should look grayed out - stroke_width: 0.5, - }, - } - } -} - -impl Interact { - pub fn style(&self, response: &Response) -> &WidgetStyle { +impl Interacted { + pub fn style(&self, response: &Response) -> &WidgetVisuals { if response.active || response.has_kb_focus { &self.active } else if response.sense == Sense::nothing() { @@ -167,7 +137,7 @@ impl Interact { #[derive(Clone, Copy, Debug)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -pub struct WidgetStyle { +pub struct WidgetVisuals { /// Background color of widget pub bg_fill: Option, @@ -189,82 +159,306 @@ pub struct WidgetStyle { pub stroke_width: f32, } -impl WidgetStyle { +impl WidgetVisuals { pub fn line_style(&self) -> LineStyle { LineStyle::new(self.stroke_width, self.stroke_color) } } -#[derive(Clone, Copy, Debug)] -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -pub struct Window { - pub corner_radius: f32, -} +// ---------------------------------------------------------------------------- -impl Default for Window { +impl Default for Style { fn default() -> Self { Self { - corner_radius: 10.0, + spacing: Spacing::default(), + interaction: Interaction::default(), + visuals: Visuals::default(), + animation_time: 1.0 / 15.0, } } } -#[derive(Clone, Copy, Debug)] -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -pub struct MenuBar { - pub height: f32, -} - -impl Default for MenuBar { +impl Default for Spacing { fn default() -> Self { - Self { height: 16.0 } + Self { + item_spacing: vec2(8.0, 4.0), + window_padding: vec2(6.0, 6.0), + button_padding: vec2(4.0, 1.0), + indent: 21.0, + clickable_diameter: 22.0, + slider_width: 140.0, + icon_width: 14.0, + menu_bar_height: 16.0, + } } } -impl Style { - // TODO: rename style.interact() to something better - /// Use this style for interactive things - pub fn interact(&self, response: &Response) -> &WidgetStyle { - self.interact.style(response) - } - - /// Returns small icon rectangle and big icon rectangle - pub fn icon_rectangles(&self, rect: Rect) -> (Rect, Rect) { - let box_side = self.start_icon_width; - let big_icon_rect = Rect::from_center_size( - pos2(rect.left() + box_side / 2.0, rect.center().y), - vec2(box_side, box_side), - ); - - 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) +impl Default for Interaction { + fn default() -> Self { + Self { + resize_grab_radius_side: 5.0, + resize_grab_radius_corner: 10.0, + } } } +impl Default for Visuals { + fn default() -> Self { + Self { + interacted: Default::default(), + text_color: Srgba::gray(160), + line_width: 1.0, + thin_outline: LineStyle::new(0.5, GRAY), + background_fill: Rgba::luminance_alpha(0.013, 0.95).into(), + dark_bg_color: Srgba::black_alpha(140), + window_corner_radius: 10.0, + resize_corner_size: 16.0, + cursor_blink_hz: None, // Some(1.0) + text_cursor_width: 2.0, + clip_rect_margin: 3.0, + debug_widget_rects: false, + debug_resize: false, + } + } +} + +impl Default for Interacted { + fn default() -> Self { + Self { + active: WidgetVisuals { + bg_fill: Some(Srgba::black_alpha(128)), + bg_outline: Some(LineStyle::new(2.0, WHITE)), + corner_radius: 0.0, + main_fill: srgba(120, 120, 200, 255), + stroke_color: WHITE, + stroke_width: 2.0, + }, + hovered: WidgetVisuals { + bg_fill: None, + bg_outline: Some(LineStyle::new(1.0, WHITE)), + corner_radius: 2.0, + main_fill: srgba(100, 100, 150, 255), + stroke_color: Srgba::gray(240), + stroke_width: 1.5, + }, + inactive: WidgetVisuals { + bg_fill: None, + bg_outline: Some(LineStyle::new(1.0, Srgba::gray(128))), + corner_radius: 4.0, + main_fill: srgba(60, 60, 80, 255), + stroke_color: Srgba::gray(200), // Mustn't look grayed out! + stroke_width: 1.0, + }, + disabled: WidgetVisuals { + bg_fill: None, + bg_outline: Some(LineStyle::new(0.5, Srgba::gray(128))), + corner_radius: 4.0, + main_fill: srgba(50, 50, 50, 255), + stroke_color: Srgba::gray(128), // Should look grayed out + stroke_width: 0.5, + }, + } + } +} + +// ---------------------------------------------------------------------------- + +use crate::{widgets::*, Ui}; + impl Style { - #[rustfmt::skip] pub fn ui(&mut self, ui: &mut crate::Ui) { - use crate::{widgets::*}; - if ui.add(Button::new("Reset style")).clicked { + if ui.add(Button::new("Reset")).clicked { *self = Default::default(); } - ui.add(Checkbox::new(&mut self.debug_widget_rects, "Paint debug rectangles around widgets")); - ui.add(Checkbox::new(&mut self.debug_resize, "Debug Resize")); - - ui.add(Slider::f32(&mut self.item_spacing.x, 0.0..=10.0).text("item_spacing.x")); - ui.add(Slider::f32(&mut self.item_spacing.y, 0.0..=10.0).text("item_spacing.y")); - ui.add(Slider::f32(&mut self.window_padding.x, 0.0..=10.0).text("window_padding.x")); - ui.add(Slider::f32(&mut self.window_padding.y, 0.0..=10.0).text("window_padding.y")); - ui.add(Slider::f32(&mut self.indent, 0.0..=100.0).text("indent")); - ui.add(Slider::f32(&mut self.button_padding.x, 0.0..=20.0).text("button_padding.x")); - ui.add(Slider::f32(&mut self.button_padding.y, 0.0..=20.0).text("button_padding.y")); - ui.add(Slider::f32(&mut self.clickable_diameter, 0.0..=60.0).text("clickable_diameter")); - ui.add(Slider::f32(&mut self.start_icon_width, 0.0..=60.0).text("start_icon_width")); - ui.add(Slider::f32(&mut self.line_width, 0.0..=10.0).text("line_width")); - ui.add(Slider::f32(&mut self.animation_time, 0.0..=1.0).text("animation_time")); + let Self { + spacing, + interaction, + visuals, + animation_time, + } = self; + ui.collapsing("Spacing", |ui| spacing.ui(ui)); + ui.collapsing("Interaction", |ui| interaction.ui(ui)); + ui.collapsing("Visuals", |ui| visuals.ui(ui)); + ui.add(Slider::f32(animation_time, 0.0..=1.0).text("animation_time")); } } + +impl Spacing { + pub fn ui(&mut self, ui: &mut crate::Ui) { + if ui.add(Button::new("Reset")).clicked { + *self = Default::default(); + } + + let Self { + item_spacing, + window_padding, + button_padding, + indent, + clickable_diameter, + slider_width, + icon_width, + menu_bar_height, + } = self; + + ui_slider_vec2(ui, item_spacing, 0.0..=20.0, "item_spacing"); + ui_slider_vec2(ui, window_padding, 0.0..=20.0, "window_padding"); + ui_slider_vec2(ui, button_padding, 0.0..=20.0, "button_padding"); + ui.add(Slider::f32(indent, 0.0..=100.0).text("indent")); + ui.add(Slider::f32(clickable_diameter, 0.0..=40.0).text("clickable_diameter")); + ui.add(Slider::f32(slider_width, 0.0..=1000.0).text("slider_width")); + ui.add(Slider::f32(icon_width, 0.0..=40.0).text("icon_width")); + ui.add(Slider::f32(menu_bar_height, 0.0..=40.0).text("menu_bar_height")); + } +} + +impl Interaction { + pub fn ui(&mut self, ui: &mut crate::Ui) { + if ui.add(Button::new("Reset")).clicked { + *self = Default::default(); + } + + let Self { + resize_grab_radius_side, + resize_grab_radius_corner, + } = self; + + ui.add(Slider::f32(resize_grab_radius_side, 0.0..=20.0).text("resize_grab_radius_side")); + ui.add( + Slider::f32(resize_grab_radius_corner, 0.0..=20.0).text("resize_grab_radius_corner"), + ); + } +} + +impl Interacted { + pub fn ui(&mut self, ui: &mut crate::Ui) { + if ui.add(Button::new("Reset")).clicked { + *self = Default::default(); + } + + let Self { + active, + hovered, + inactive, + disabled, + } = self; + + ui.collapsing("active", |ui| active.ui(ui)); + ui.collapsing("hovered", |ui| hovered.ui(ui)); + ui.collapsing("inactive", |ui| inactive.ui(ui)); + ui.collapsing("disabled", |ui| disabled.ui(ui)); + } +} + +impl WidgetVisuals { + pub fn ui(&mut self, ui: &mut crate::Ui) { + let Self { + bg_fill, + bg_outline, + corner_radius, + main_fill, + stroke_color, + stroke_width, + } = self; + + let _ = bg_fill; // ui_color(ui, bg_fill, "bg_fill"); // TODO + let _ = bg_outline; // bg_outline.ui(ui, "bg_outline");// TODO + ui.add(Slider::f32(corner_radius, 0.0..=10.0).text("corner_radius")); + ui_color(ui, main_fill, "main_fill"); + ui_color(ui, stroke_color, "stroke_color"); + ui.add(Slider::f32(stroke_width, 0.0..=10.0).text("stroke_width")); + } +} + +impl Visuals { + pub fn ui(&mut self, ui: &mut crate::Ui) { + if ui.add(Button::new("Reset")).clicked { + *self = Default::default(); + } + + let Self { + interacted, + text_color, + line_width, + thin_outline, + background_fill, + dark_bg_color, + window_corner_radius, + resize_corner_size, + cursor_blink_hz, + text_cursor_width, + clip_rect_margin, + debug_widget_rects, + debug_resize, + } = self; + + ui.collapsing("interacted", |ui| interacted.ui(ui)); + ui_color(ui, text_color, "text_color"); + ui.add(Slider::f32(line_width, 0.0..=10.0).text("line_width")); + thin_outline.ui(ui, "thin_outline"); + ui_color(ui, background_fill, "background_fill"); + ui_color(ui, dark_bg_color, "dark_bg_color"); + ui.add(Slider::f32(window_corner_radius, 0.0..=20.0).text("window_corner_radius")); + ui.add(Slider::f32(resize_corner_size, 0.0..=20.0).text("resize_corner_size")); + let _ = cursor_blink_hz; // TODO + ui.add(Slider::f32(text_cursor_width, 0.0..=2.0).text("text_cursor_width")); + ui.add(Slider::f32(clip_rect_margin, 0.0..=20.0).text("clip_rect_margin")); + + ui.add(Checkbox::new( + debug_widget_rects, + "Paint debug rectangles around widgets", + )); + ui.add(Checkbox::new(debug_resize, "Debug Resize")); + } +} + +impl LineStyle { + pub fn ui(&mut self, ui: &mut crate::Ui, text: &str) { + let Self { width, color } = self; + ui.horizontal_centered(|ui| { + ui.label(format!("{}: ", text)); + ui.add(Slider::f32(width, 0.0..=10.0)); + ui_color(ui, color, "color"); + }); + } +} + +// TODO: improve and standardize ui_slider_vec2 +fn ui_slider_vec2(ui: &mut Ui, value: &mut Vec2, range: std::ops::RangeInclusive, text: &str) { + ui.horizontal_centered(|ui| { + ui.label(format!("{}: ", text)); + ui.add(Slider::f32(&mut value.x, range.clone())) + .tooltip_text("x"); + ui.add(Slider::f32(&mut value.y, range)).tooltip_text("y"); + }); +} + +// TODO: improve color picker +fn ui_color(ui: &mut Ui, srgba: &mut Srgba, text: &str) { + ui.horizontal_centered(|ui| { + // TODO: DragValue::u8 + // ui.label(format!("{} sRGBA: ", text)); + // ui.add(DragValue::u8(&mut srgba.r).speed(1)) + // .tooltip_text("r"); + // ui.add(DragValue::u8(&mut srgba.g).speed(1)) + // .tooltip_text("g"); + // ui.add(DragValue::u8(&mut srgba.b).speed(1)) + // .tooltip_text("b"); + // ui.add(DragValue::u8(&mut srgba.a).speed(1)) + // .tooltip_text("a"); + + ui.label(format!("{} RGBA: ", text)); + let mut rgba = Rgba::from(*srgba); + ui.add(DragValue::f32(&mut rgba.r).speed(0.003)) + .tooltip_text("r"); + ui.add(DragValue::f32(&mut rgba.g).speed(0.003)) + .tooltip_text("g"); + ui.add(DragValue::f32(&mut rgba.b).speed(0.003)) + .tooltip_text("b"); + ui.add(DragValue::f32(&mut rgba.a).speed(0.003)) + .tooltip_text("a"); + if rgba != Rgba::from(*srgba) { + *srgba = Srgba::from(rgba); + } + }); +} diff --git a/egui/src/ui.rs b/egui/src/ui.rs index a80a040b..4f3a3087 100644 --- a/egui/src/ui.rs +++ b/egui/src/ui.rs @@ -38,8 +38,8 @@ pub struct Ui { /// Where the next widget will be put. /// Progresses along self.dir. /// Initially set to rect.min - /// If something has already been added, this will point ot style.item_spacing beyond the latest child. - /// The cursor can thus be style.item_spacing pixels outside of the child_bounds. + /// If something has already been added, this will point ot style.spacing.item_spacing beyond the latest child. + /// The cursor can thus be style.spacing.item_spacing pixels outside of the child_bounds. cursor: Pos2, // TODO: move into Layout? /// How many children has been added to us? @@ -54,7 +54,7 @@ impl Ui { pub fn new(ctx: Arc, layer: Layer, id: Id, rect: Rect) -> Self { let style = ctx.style(); - let clip_rect = rect.expand(style.clip_rect_margin); + let clip_rect = rect.expand(style.visuals.clip_rect_margin); Ui { id, painter: Painter::new(ctx, layer, clip_rect), @@ -75,7 +75,7 @@ impl Ui { painter: self.painter.clone(), desired_rect: child_rect, child_bounds: Rect::from_min_size(child_rect.min, Vec2::zero()), // TODO: Rect::nothing() ? - style: self.style.clone(), + style: self.style().clone(), layout: self.layout, cursor: child_rect.min, child_count: 0, @@ -366,7 +366,7 @@ impl Ui { let rect = self.reserve_space_impl(desired_size); - if self.style().debug_widget_rects { + if self.style().visuals.debug_widget_rects { self.painter.rect_outline(rect, 0.0, (1.0, LIGHT_BLUE)); let color = color::srgba(200, 0, 0, 255); @@ -535,7 +535,7 @@ impl Ui { self.layout().dir() == Direction::Vertical, "You can only indent vertical layouts" ); - let indent = vec2(self.style.indent, 0.0); + let indent = vec2(self.style().spacing.indent, 0.0); let child_rect = Rect::from_min_max(self.cursor + indent, self.bottom_right()); let mut child_ui = Ui { id: self.id.with(id_source), @@ -550,7 +550,7 @@ impl Ui { let line_end = pos2(line_start.x, line_start.y + size.y - 2.0); self.painter.line_segment( [line_start, line_end], - (self.style.line_width, Srgba::gray(150)), + (self.style().visuals.line_width, Srgba::gray(150)), ); (ret, self.allocate_space(indent + size)) @@ -631,7 +631,7 @@ impl Ui { F: FnOnce(&mut [Self]) -> R, { // TODO: ensure there is space - let spacing = self.style.item_spacing.x; + let spacing = self.style().spacing.item_spacing.x; let total_spacing = spacing * (num_columns as f32 - 1.0); let column_width = (self.available().width() - total_spacing) / (num_columns as f32); diff --git a/egui/src/widgets.rs b/egui/src/widgets.rs index 8ef89eb7..9d9213c3 100644 --- a/egui/src/widgets.rs +++ b/egui/src/widgets.rs @@ -107,7 +107,9 @@ impl Label { // This should be the easiest method of putting text anywhere. pub fn paint_galley(&self, ui: &mut Ui, pos: Pos2, galley: font::Galley) { - let text_color = self.text_color.unwrap_or_else(|| ui.style().text_color); + let text_color = self + .text_color + .unwrap_or_else(|| ui.style().visuals.text_color); ui.painter() .galley(pos, galley, self.text_style, text_color); } @@ -201,7 +203,7 @@ impl Widget for Hyperlink { let max_x = pos.x + line.max_x(); ui.painter().line_segment( [pos2(min_x, y), pos2(max_x, y)], - (ui.style().line_width, color), + (ui.style().visuals.line_width, color), ); } } @@ -281,9 +283,9 @@ impl Widget for Button { let id = ui.make_position_id(); let font = &ui.fonts()[text_style]; let galley = font.layout_multiline(text, ui.available().width()); - let padding = ui.style().button_padding; + let padding = ui.style().spacing.button_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().spacing.clickable_diameter); let rect = ui.allocate_space(size); let response = ui.interact(rect, id, sense); let text_cursor = response.rect.left_center() + vec2(padding.x, -0.5 * galley.size.y); @@ -340,18 +342,19 @@ impl<'a> Widget for Checkbox<'a> { let text_style = TextStyle::Button; let font = &ui.fonts()[text_style]; let galley = font.layout_single_line(text); - let size = ui.style().button_padding - + vec2(ui.style().start_icon_width, 0.0) + let size = ui.style().spacing.button_padding + + vec2(ui.style().spacing.icon_width, 0.0) + galley.size - + ui.style().button_padding; + + ui.style().spacing.button_padding; let rect = ui.allocate_space(size); let response = ui.interact(rect, id, Sense::click()); - let text_cursor = - response.rect.min + ui.style().button_padding + vec2(ui.style().start_icon_width, 0.0); + let text_cursor = response.rect.min + + ui.style().spacing.button_padding + + vec2(ui.style().spacing.icon_width, 0.0); if response.clicked { *checked = !*checked; } - let (small_icon_rect, big_icon_rect) = ui.style().icon_rectangles(response.rect); + let (small_icon_rect, big_icon_rect) = ui.style().spacing.icon_rectangles(response.rect); ui.painter().add(PaintCmd::Rect { rect: big_icon_rect, corner_radius: ui.style().interact(&response).corner_radius, @@ -369,7 +372,7 @@ impl<'a> Widget for Checkbox<'a> { pos2(small_icon_rect.right(), small_icon_rect.top()), ]), closed: false, - outline: Some(LineStyle::new(ui.style().line_width, stroke_color)), + outline: Some(LineStyle::new(ui.style().visuals.line_width, stroke_color)), fill: None, }); } @@ -417,19 +420,20 @@ impl Widget for RadioButton { let text_style = TextStyle::Button; let font = &ui.fonts()[text_style]; let galley = font.layout_multiline(text, ui.available().width()); - let size = ui.style().button_padding - + vec2(ui.style().start_icon_width, 0.0) + let size = ui.style().spacing.button_padding + + vec2(ui.style().spacing.icon_width, 0.0) + galley.size - + ui.style().button_padding; + + ui.style().spacing.button_padding; let rect = ui.allocate_space(size); let response = ui.interact(rect, id, Sense::click()); - let text_cursor = - response.rect.min + ui.style().button_padding + vec2(ui.style().start_icon_width, 0.0); + let text_cursor = response.rect.min + + ui.style().spacing.button_padding + + vec2(ui.style().spacing.icon_width, 0.0); let bg_fill = ui.style().interact(&response).bg_fill; let stroke_color = ui.style().interact(&response).stroke_color; - let (small_icon_rect, big_icon_rect) = ui.style().icon_rectangles(response.rect); + let (small_icon_rect, big_icon_rect) = ui.style().spacing.icon_rectangles(response.rect); let painter = ui.painter(); @@ -507,7 +511,7 @@ impl Widget for Separator { color, } = self; - let line_width = line_width.unwrap_or_else(|| ui.style().line_width); + let line_width = line_width.unwrap_or_else(|| ui.style().visuals.line_width); let available_space = ui.available_finite().size(); diff --git a/egui/src/widgets/slider.rs b/egui/src/widgets/slider.rs index 1ee23211..d82dfe0a 100644 --- a/egui/src/widgets/slider.rs +++ b/egui/src/widgets/slider.rs @@ -126,7 +126,7 @@ impl<'a> Slider<'a> { /// Just the slider, no text fn allocate_slide_space(&self, ui: &mut Ui, height: f32) -> Response { let id = self.id.unwrap_or_else(|| ui.make_position_id()); - let desired_size = vec2(ui.available().width(), height); + let desired_size = vec2(ui.style().spacing.slider_width, height); let rect = ui.allocate_space(desired_size); ui.interact(rect, id, Sense::click_and_drag()) } @@ -164,7 +164,7 @@ impl<'a> Slider<'a> { ui.painter().add(PaintCmd::Rect { rect: rail_rect, corner_radius: rail_radius, - fill: Some(ui.style().background_fill), + fill: Some(ui.style().visuals.background_fill), outline: Some(LineStyle::new(1.0, Srgba::gray(200))), // TODO }); @@ -182,10 +182,12 @@ impl<'a> Slider<'a> { /// Just the text label fn text_ui(&mut self, ui: &mut Ui, x_range: RangeInclusive) { - let text_color = self.text_color.unwrap_or_else(|| ui.style().text_color); + let text_color = self + .text_color + .unwrap_or_else(|| ui.style().visuals.text_color); if let Some(label_text) = self.text.as_deref() { - ui.style_mut().item_spacing.x = 0.0; + ui.style_mut().spacing.item_spacing.x = 0.0; ui.add( Label::new(format!("{}: ", label_text)) .multiline(false) @@ -258,30 +260,21 @@ impl<'a> Widget for Slider<'a> { fn ui(mut self, ui: &mut Ui) -> Response { let text_style = TextStyle::Button; let font = &ui.fonts()[text_style]; - let height = font.line_spacing().max(ui.style().clickable_diameter); + let height = font + .line_spacing() + .max(ui.style().spacing.clickable_diameter); if let Some(text) = &self.text { self.id = self.id.or_else(|| Some(ui.make_unique_child_id(text))); - ui.columns(2, |columns| { - let slider_ui = &mut columns[0]; - let slider_response = self.allocate_slide_space(slider_ui, height); - self.slider_ui(slider_ui, &slider_response); + ui.horizontal_centered(|ui| { + let slider_response = self.allocate_slide_space(ui, height); + self.slider_ui(ui, &slider_response); let x_range = x_range(&slider_response.rect); - - // Place the text in line with the slider on the left: - let text_ui = &mut columns[1]; - text_ui.set_desired_height(slider_response.rect.height()); - text_ui.inner_layout( - Layout::horizontal(Align::Center), - text_ui.available().size(), - |ui| { - self.text_ui(ui, x_range); - }, - ); - + self.text_ui(ui, x_range); slider_response }) + .0 } else { let response = self.allocate_slide_space(ui, height); self.slider_ui(ui, &response); diff --git a/egui/src/widgets/text_edit.rs b/egui/src/widgets/text_edit.rs index 69608861..1bbf7263 100644 --- a/egui/src/widgets/text_edit.rs +++ b/egui/src/widgets/text_edit.rs @@ -183,13 +183,13 @@ impl<'t> Widget for TextEdit<'t> { painter.add(PaintCmd::Rect { rect: bg_rect, corner_radius: ui.style().interact(&response).corner_radius, - fill: Some(ui.style().dark_bg_color), + fill: Some(ui.style().visuals.dark_bg_color), outline: ui.style().interact(&response).bg_outline, }); } if ui.memory().has_kb_focus(id) { - let cursor_blink_hz = ui.style().cursor_blink_hz; + let cursor_blink_hz = ui.style().visuals.cursor_blink_hz; let show_cursor = if let Some(cursor_blink_hz) = cursor_blink_hz { ui.ctx().request_repaint(); // TODO: only when cursor blinks on or off (ui.input().time * cursor_blink_hz as f64 * 3.0).floor() as i64 % 3 != 0 @@ -202,7 +202,7 @@ impl<'t> Widget for TextEdit<'t> { let cursor_pos = response.rect.min + galley.char_start_pos(cursor); painter.line_segment( [cursor_pos, cursor_pos + vec2(0.0, line_spacing)], - (ui.style().text_cursor_width, color::WHITE), + (ui.style().visuals.text_cursor_width, color::WHITE), ); } }