Highlight window frame when you resize it

This commit is contained in:
Emil Ernerfeldt 2020-05-23 11:28:21 +02:00
parent fac0866f73
commit ee0ad02717
10 changed files with 263 additions and 111 deletions

View File

@ -91,15 +91,15 @@ impl Area {
} }
} }
struct Prepared { pub(crate) struct Prepared {
layer: Layer, layer: Layer,
state: State, pub(crate) state: State,
movable: bool, movable: bool,
content_ui: Ui, pub(crate) content_ui: Ui,
} }
impl Area { impl Area {
fn prepare(self, ctx: &Arc<Context>) -> Prepared { pub(crate) fn begin(self, ctx: &Arc<Context>) -> Prepared {
let Area { let Area {
id, id,
movable, movable,
@ -138,18 +138,20 @@ impl Area {
} }
pub fn show(self, ctx: &Arc<Context>, add_contents: impl FnOnce(&mut Ui)) -> InteractInfo { pub fn show(self, ctx: &Arc<Context>, add_contents: impl FnOnce(&mut Ui)) -> InteractInfo {
let mut prepared = self.prepare(ctx); let mut prepared = self.begin(ctx);
add_contents(&mut prepared.content_ui); add_contents(&mut prepared.content_ui);
Self::finish(ctx, prepared) prepared.end(ctx)
} }
}
fn finish(ctx: &Arc<Context>, prepared: Prepared) -> InteractInfo { impl Prepared {
pub(crate) fn end(self, ctx: &Arc<Context>) -> InteractInfo {
let Prepared { let Prepared {
layer, layer,
mut state, mut state,
movable, movable,
content_ui, content_ui,
} = prepared; } = self;
state.size = (content_ui.child_bounds().max - state.pos).ceil(); state.size = (content_ui.child_bounds().max - state.pos).ceil();

View File

@ -168,7 +168,7 @@ struct Prepared {
} }
impl CollapsingHeader { impl CollapsingHeader {
fn prepare(self, ui: &mut Ui) -> Prepared { fn begin(self, ui: &mut Ui) -> Prepared {
assert!( assert!(
ui.layout().dir() == Direction::Vertical, ui.layout().dir() == Direction::Vertical,
"Horizontal collapsing is unimplemented" "Horizontal collapsing is unimplemented"
@ -185,7 +185,7 @@ impl CollapsingHeader {
let available = ui.available_finite(); let available = ui.available_finite();
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_width(ui, available.width() - ui.style().indent);
let text_max_x = text_pos.x + galley.size.x; let text_max_x = text_pos.x + galley.size.x;
let desired_width = text_max_x - available.left(); let desired_width = text_max_x - available.left();
let desired_width = desired_width.max(available.width()); let desired_width = desired_width.max(available.width());
@ -240,7 +240,7 @@ impl CollapsingHeader {
} }
pub fn show<R>(self, ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) -> Option<R> { pub fn show<R>(self, ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) -> Option<R> {
let Prepared { id, mut state } = self.prepare(ui); let Prepared { id, mut state } = self.begin(ui);
let r_interact = state.add_contents(ui, |ui| ui.indent(id, add_contents).0); let r_interact = state.add_contents(ui, |ui| ui.indent(id, add_contents).0);
let ret = r_interact.map(|ri| ri.0); let ret = r_interact.map(|ri| ri.0);
ui.memory().collapsing_headers.insert(id, state); ui.memory().collapsing_headers.insert(id, state);

View File

@ -17,7 +17,7 @@ impl Frame {
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: style.interact.inactive.rect_outline, // becauce we can resize windows
} }
} }
@ -59,30 +59,58 @@ impl Frame {
} }
} }
pub struct Prepared {
pub frame: Frame,
outer_rect_bounds: Rect,
where_to_put_background: usize,
pub content_ui: Ui,
}
impl Frame { impl Frame {
pub fn show<R>(self, ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) -> R { pub fn begin(self, ui: &mut Ui) -> Prepared {
let Frame { let outer_rect_bounds = ui.available();
margin, let inner_rect = outer_rect_bounds.shrink2(self.margin);
corner_radius,
fill_color,
outline,
} = self;
let outer_rect = ui.available();
let inner_rect = outer_rect.shrink2(margin);
let where_to_put_background = ui.paint_list_len(); let where_to_put_background = ui.paint_list_len();
let content_ui = ui.child_ui(inner_rect);
Prepared {
frame: self,
outer_rect_bounds,
where_to_put_background,
content_ui,
}
}
let mut child_ui = ui.child_ui(inner_rect); pub fn show<R>(self, ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) -> R {
let ret = add_contents(&mut child_ui); let mut prepared = self.begin(ui);
let ret = add_contents(&mut prepared.content_ui);
prepared.end(ui);
ret
}
}
let outer_rect = Rect::from_min_max(outer_rect.min, child_ui.child_bounds().max + margin); impl Prepared {
pub fn outer_rect(&self) -> Rect {
Rect::from_min_max(
self.outer_rect_bounds.min,
self.content_ui.child_bounds().max + self.frame.margin,
)
}
pub fn end(self, ui: &mut Ui) -> Rect {
let outer_rect = self.outer_rect();
let Prepared {
frame,
where_to_put_background,
..
} = self;
ui.insert_paint_cmd( ui.insert_paint_cmd(
where_to_put_background, where_to_put_background,
PaintCmd::Rect { PaintCmd::Rect {
corner_radius, corner_radius: frame.corner_radius,
fill_color, fill_color: frame.fill_color,
outline, outline: frame.outline,
rect: outer_rect, rect: outer_rect,
}, },
); );
@ -90,6 +118,6 @@ impl Frame {
ui.expand_to_include_child(outer_rect); ui.expand_to_include_child(outer_rect);
// TODO: move cursor in parent ui // TODO: move cursor in parent ui
ret outer_rect
} }
} }

View File

@ -177,7 +177,7 @@ struct Prepared {
} }
impl Resize { impl Resize {
fn prepare(&mut self, ui: &mut Ui) -> Prepared { fn begin(&mut self, ui: &mut Ui) -> Prepared {
let id = self.id.unwrap_or_else(|| ui.make_child_id("resize")); let id = self.id.unwrap_or_else(|| ui.make_child_id("resize"));
self.min_size = self.min_size.min(ui.available().size()); self.min_size = self.min_size.min(ui.available().size());
self.max_size = self.max_size.min(ui.available().size()); self.max_size = self.max_size.min(ui.available().size());
@ -267,13 +267,13 @@ impl Resize {
} }
pub fn show<R>(mut self, ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) -> R { pub fn show<R>(mut self, ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) -> R {
let mut prepared = self.prepare(ui); let mut prepared = self.begin(ui);
let ret = add_contents(&mut prepared.content_ui); let ret = add_contents(&mut prepared.content_ui);
self.finish(ui, prepared); self.end(ui, prepared);
ret ret
} }
fn finish(self, ui: &mut Ui, prepared: Prepared) { fn end(self, ui: &mut Ui, prepared: Prepared) {
let Prepared { let Prepared {
id, id,
mut state, mut state,

View File

@ -54,7 +54,7 @@ struct Prepared {
} }
impl ScrollArea { impl ScrollArea {
fn prepare(self, ui: &mut Ui) -> Prepared { fn begin(self, ui: &mut Ui) -> Prepared {
let Self { let Self {
max_height, max_height,
always_show_scroll, always_show_scroll,
@ -110,13 +110,15 @@ impl ScrollArea {
} }
pub fn show<R>(self, ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) -> R { pub fn show<R>(self, ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) -> R {
let mut prepared = self.prepare(ui); let mut prepared = self.begin(ui);
let ret = add_contents(&mut prepared.content_ui); let ret = add_contents(&mut prepared.content_ui);
Self::finish(ui, prepared); prepared.end(ui);
ret ret
} }
}
fn finish(ui: &mut Ui, prepared: Prepared) { impl Prepared {
fn end(self, ui: &mut Ui) {
let Prepared { let Prepared {
id, id,
mut state, mut state,
@ -124,7 +126,7 @@ impl ScrollArea {
always_show_scroll, always_show_scroll,
current_scroll_bar_width, current_scroll_bar_width,
content_ui, content_ui,
} = prepared; } = self;
let content_size = content_ui.bounding_size(); let content_size = content_ui.bounding_size();

View File

@ -148,22 +148,31 @@ impl<'open> Window<'open> {
return None; return None;
} }
let movable = area.is_movable();
let area = area.movable(false); // We move it manually
let resizable = resize.is_resizable();
let resize = resize.resizable(false); // We move it manually
let window_id = Id::new(title_label.text()); let window_id = Id::new(title_label.text());
let area_layer = area.layer(); let area_layer = area.layer();
let resize_id = window_id.with("resize"); let resize_id = window_id.with("resize");
let collapsing_id = window_id.with("collapsing"); let collapsing_id = window_id.with("collapsing");
let possible = PossibleInteractions {
movable: area.is_movable(),
resizable: resize.is_resizable()
&& collapsing_header::State::is_open(ctx, collapsing_id).unwrap_or_default(),
};
let area = area.movable(false); // We move it manually
let resize = resize.resizable(false); // We move it manually
let resize = resize.id(resize_id); let resize = resize.id(resize_id);
let frame = frame.unwrap_or_else(|| Frame::window(&ctx.style())); let frame = frame.unwrap_or_else(|| Frame::window(&ctx.style()));
let full_interact = area.show(ctx, |ui| { let mut area = area.begin(ctx);
frame.show(ui, |ui| { {
// TODO: pick style for frame and title based on interaction
let mut frame = frame.begin(&mut area.content_ui);
{
let ui = &mut frame.content_ui;
let default_expanded = true; let default_expanded = true;
let mut collapsing = collapsing_header::State::from_memory_with_default_open( let mut collapsing = collapsing_header::State::from_memory_with_default_open(
ui, ui,
@ -182,7 +191,7 @@ impl<'open> Window<'open> {
let content = collapsing let content = collapsing
.add_contents(ui, |ui| { .add_contents(ui, |ui| {
resize.show(ui, |ui| { resize.show(ui, |ui| {
ui.add(Separator::new().line_width(1.0)); // TODO: nicer way to split window title from contents ui.add(Separator::new().line_width(0.5)); // TODO: nicer way to split window title from contents
if let Some(scroll) = scroll { if let Some(scroll) = scroll {
scroll.show(ui, add_contents) scroll.show(ui, add_contents)
} else { } else {
@ -198,42 +207,49 @@ impl<'open> Window<'open> {
if let Some(open) = open { if let Some(open) = open {
// Add close button now that we know our full width: // Add close button now that we know our full width:
if title_bar.close_button_ui(ui, &content).clicked {
let right = content
.map(|c| c.rect.right())
.unwrap_or(title_bar.rect.right());
let button_size = ui.style().start_icon_width;
let button_rect = Rect::from_min_size(
pos2(
right - ui.style().item_spacing.x - button_size,
title_bar.rect.center().y - 0.5 * button_size,
),
Vec2::splat(button_size),
);
if close_button(ui, button_rect).clicked {
*open = false; *open = false;
} }
} }
}) title_bar.title_ui(ui);
}); }
let resizable = let outer_rect = frame.end(&mut area.content_ui);
resizable && collapsing_header::State::is_open(ctx, collapsing_id).unwrap_or_default();
if movable || resizable { let interaction = if possible.movable || possible.resizable {
let possible = PossibleInteractions { movable, resizable }; interact(
ctx,
possible,
area_layer,
&mut area.state,
window_id,
resize_id,
outer_rect,
)
} else {
None
};
interact( if let Some(interaction) = interaction {
ctx, paint_frame_interaction(
possible, &mut area.content_ui,
area_layer, outer_rect,
window_id, interaction,
resize_id, ctx.style().interact.active,
full_interact.rect, );
); } else {
if let Some(hover_interaction) = resize_hover(ctx, possible, area_layer, outer_rect)
{
paint_frame_interaction(
&mut area.content_ui,
outer_rect,
hover_interaction,
ctx.style().interact.hovered,
);
}
}
} }
let full_interact = area.end(ctx);
Some(full_interact) Some(full_interact)
} }
@ -280,10 +296,11 @@ fn interact(
ctx: &Context, ctx: &Context,
possible: PossibleInteractions, possible: PossibleInteractions,
area_layer: Layer, area_layer: Layer,
area_state: &mut area::State,
window_id: Id, window_id: Id,
resize_id: Id, resize_id: Id,
rect: Rect, rect: Rect,
) -> Option<()> { ) -> Option<WindowInteraction> {
let pre_resize = ctx.round_rect_to_pixels(rect); let pre_resize = ctx.round_rect_to_pixels(rect);
let window_interaction = window_interaction( let window_interaction = window_interaction(
ctx, ctx,
@ -297,9 +314,7 @@ fn interact(
let new_rect = ctx.round_rect_to_pixels(new_rect); let new_rect = ctx.round_rect_to_pixels(new_rect);
// TODO: add this to a Window state instead as a command "move here next frame" // TODO: add this to a Window state instead as a command "move here next frame"
let mut area_state = ctx.memory().areas.get(area_layer.id).unwrap();
area_state.pos = new_rect.min; area_state.pos = new_rect.min;
ctx.memory().areas.set_state(area_layer, area_state);
let mut resize_state = ctx.memory().resize.get(&resize_id).cloned().unwrap(); let mut resize_state = ctx.memory().resize.get(&resize_id).cloned().unwrap();
// resize_state.size += new_rect.size() - pre_resize.size(); // resize_state.size += new_rect.size() - pre_resize.size();
@ -308,7 +323,7 @@ fn interact(
ctx.memory().resize.insert(resize_id, resize_state); ctx.memory().resize.insert(resize_id, resize_state);
ctx.memory().areas.move_to_top(area_layer); ctx.memory().areas.move_to_top(area_layer);
Some(()) Some(window_interaction)
} }
fn resize_window(ctx: &Context, window_interaction: &WindowInteraction) -> Option<Rect> { fn resize_window(ctx: &Context, window_interaction: &WindowInteraction) -> Option<Rect> {
@ -446,16 +461,69 @@ fn resize_hover(
} }
} }
/// Fill in parts of the window frame when we resize by dragging that part
fn paint_frame_interaction(
ui: &mut Ui,
rect: Rect,
interaction: WindowInteraction,
style: style::WidgetStyle,
) {
let cr = ui.style().window.corner_radius;
let Rect { min, max } = rect;
let mut path = Path::default();
if interaction.right && !interaction.bottom && !interaction.top {
path.add_line(&[pos2(max.x, min.y + cr), pos2(max.x, max.y - cr)]);
}
if interaction.right && interaction.bottom {
path.add_line(&[pos2(max.x, min.y + cr), pos2(max.x, max.y - cr)]);
path.add_circle_quadrant(pos2(max.x - cr, max.y - cr), cr, 0.0);
}
if interaction.bottom {
path.add_line(&[pos2(max.x - cr, max.y), pos2(min.x + cr, max.y)]);
}
if interaction.left && interaction.bottom {
path.add_circle_quadrant(pos2(min.x + cr, max.y - cr), cr, 1.0);
}
if interaction.left {
path.add_line(&[pos2(min.x, max.y - cr), pos2(min.x, min.y + cr)]);
}
if interaction.left && interaction.top {
path.add_circle_quadrant(pos2(min.x + cr, min.y + cr), cr, 2.0);
}
if interaction.top {
path.add_line(&[pos2(min.x + cr, min.y), pos2(max.x - cr, min.y)]);
}
if interaction.right && interaction.top {
path.add_circle_quadrant(pos2(max.x - cr, min.y + cr), cr, 3.0);
path.add_line(&[pos2(max.x, min.y + cr), pos2(max.x, max.y - cr)]);
}
ui.add_paint_cmd(PaintCmd::Path {
path,
closed: false,
fill_color: None,
outline: style.rect_outline,
});
}
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
struct TitleBar {
title_label: Label,
title_galley: font::Galley,
title_rect: Rect,
rect: Rect,
}
fn show_title_bar( fn show_title_bar(
ui: &mut Ui, ui: &mut Ui,
title_label: Label, title_label: Label,
show_close_button: bool, show_close_button: bool,
collapsing_id: Id, collapsing_id: Id,
collapsing: &mut collapsing_header::State, collapsing: &mut collapsing_header::State,
) -> InteractInfo { ) -> TitleBar {
ui.inner_layout(Layout::horizontal(Align::Center), |ui| { let tb_interact = ui.inner_layout(Layout::horizontal(Align::Center), |ui| {
ui.set_desired_height(title_label.font_height(ui)); ui.set_desired_height(title_label.font_height(ui));
let item_spacing = ui.style().item_spacing; let item_spacing = ui.style().item_spacing;
@ -474,7 +542,8 @@ fn show_title_bar(
collapsing.paint_icon(ui, &collapse_button_interact); collapsing.paint_icon(ui, &collapse_button_interact);
} }
let title_rect = ui.add(title_label).rect; let title_galley = title_label.layout(ui);
let title_rect = ui.reserve_space(title_galley.size, None).rect;
if show_close_button { if show_close_button {
// Reserve space for close button which will be added later: // Reserve space for close button which will be added later:
@ -489,8 +558,41 @@ fn show_title_bar(
); );
ui.expand_to_include_child(close_rect); ui.expand_to_include_child(close_rect);
} }
})
.1 TitleBar {
title_label,
title_galley,
title_rect,
rect: Default::default(), // Will be filled in later
}
});
TitleBar {
rect: tb_interact.1.rect,
..tb_interact.0
}
}
impl TitleBar {
pub fn title_ui(self, ui: &mut Ui) {
self.title_label
.paint_galley(ui, self.title_rect.min, self.title_galley);
}
pub fn close_button_ui(&self, ui: &mut Ui, content: &Option<InteractInfo>) -> InteractInfo {
let right = content.map(|c| c.rect.right()).unwrap_or(self.rect.right());
let button_size = ui.style().start_icon_width;
let button_rect = Rect::from_min_size(
pos2(
right - ui.style().item_spacing.x - button_size,
self.rect.center().y - 0.5 * button_size,
),
Vec2::splat(button_size),
);
close_button(ui, button_rect)
}
} }
fn close_button(ui: &mut Ui, rect: Rect) -> InteractInfo { fn close_button(ui: &mut Ui, rect: Rect) -> InteractInfo {

View File

@ -497,10 +497,10 @@ impl LayoutExample {
pub fn ui(&mut self, ui: &mut Ui) { pub fn ui(&mut self, ui: &mut Ui) {
Resize::default() Resize::default()
.default_size(vec2(200.0, 200.0)) .default_size(vec2(200.0, 200.0))
.show(ui, |ui| self.contents_ui(ui)); .show(ui, |ui| self.content_ui(ui));
} }
pub fn contents_ui(&mut self, ui: &mut Ui) { pub fn content_ui(&mut self, ui: &mut Ui) {
let layout = Layout::from_dir_align(self.dir, self.align); let layout = Layout::from_dir_align(self.dir, self.align);
if self.reversed { if self.reversed {
ui.set_layout(layout.reverse()); ui.set_layout(layout.reverse());

View File

@ -163,6 +163,14 @@ impl Path {
self.0.clear(); self.0.clear();
} }
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn len(&self) -> usize {
self.0.len()
}
#[inline(always)] #[inline(always)]
pub fn add_point(&mut self, pos: Pos2, normal: Vec2) { pub fn add_point(&mut self, pos: Pos2, normal: Vec2) {
self.0.push(PathPoint { pos, normal }); self.0.push(PathPoint { pos, normal });
@ -252,13 +260,13 @@ impl Path {
/// with x right, and y down (GUI coords) we have: /// with x right, and y down (GUI coords) we have:
/// angle = dir /// angle = dir
/// 0 * TAU / 4 = right /// 0 * TAU / 4 = right
/// quadrant 0, right down /// quadrant 0, right bottom
/// 1 * TAU / 4 = down /// 1 * TAU / 4 = bottom
/// quadrant 1, down left /// quadrant 1, left bottom
/// 2 * TAU / 4 = left /// 2 * TAU / 4 = left
/// quadrant 2 left up /// quadrant 2 left top
/// 3 * TAU / 4 = up /// 3 * TAU / 4 = top
/// quadrant 3 up rigth /// quadrant 3 right top
/// 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 = (radius * 0.5).round() as i32; // TODO: tweak a bit more let n = (radius * 0.5).round() as i32; // TODO: tweak a bit more
@ -581,16 +589,18 @@ pub fn paint_command_into_triangles(
fill_color, fill_color,
outline, outline,
} => { } => {
if let Some(fill_color) = fill_color { if path.len() >= 2 {
debug_assert!( if let Some(fill_color) = fill_color {
closed, debug_assert!(
"You asked to fill a path that is not closed. That makes no sense." closed,
); "You asked to fill a path that is not closed. That makes no sense."
fill_closed_path(out, options, &path.0, fill_color); );
} fill_closed_path(out, options, &path.0, fill_color);
if let Some(outline) = outline { }
let typ = if closed { Closed } else { Open }; if let Some(outline) = outline {
paint_path_outline(out, options, typ, &path.0, outline.color, outline.width); let typ = if closed { Closed } else { Open };
paint_path_outline(out, options, typ, &path.0, outline.color, outline.width);
}
} }
} }
PaintCmd::Rect { PaintCmd::Rect {

View File

@ -120,7 +120,7 @@ impl Default for Interact {
fill_color: srgba(60, 60, 80, 255), fill_color: srgba(60, 60, 80, 255),
stroke_color: gray(210, 255), // Mustn't look grayed out! stroke_color: gray(210, 255), // Mustn't look grayed out!
stroke_width: 1.0, stroke_width: 1.0,
rect_outline: Some(Outline::new(0.5, WHITE)), rect_outline: Some(Outline::new(1.0, white(128))),
corner_radius: 0.0, corner_radius: 0.0,
}, },
} }

View File

@ -63,7 +63,16 @@ impl Label {
self self
} }
pub fn layout(&self, max_width: f32, ui: &Ui) -> font::Galley { pub fn layout(&self, ui: &Ui) -> font::Galley {
let max_width = if self.auto_shrink {
ui.available_finite().width()
} else {
ui.available().width()
};
self.layout_width(ui, max_width)
}
pub fn layout_width(&self, ui: &Ui, max_width: f32) -> font::Galley {
let font = &ui.fonts()[self.text_style]; let font = &ui.fonts()[self.text_style];
if self.multiline { if self.multiline {
font.layout_multiline(self.text.clone(), max_width) // TODO: avoid clone font.layout_multiline(self.text.clone(), max_width) // TODO: avoid clone
@ -83,6 +92,10 @@ impl Label {
// TODO: a paint method for painting anywhere in a ui. // TODO: a paint method for painting anywhere in a ui.
// This should be the easiest method of putting text anywhere. // This should be the easiest method of putting text anywhere.
pub fn paint_galley(&self, ui: &mut Ui, pos: Pos2, galley: font::Galley) {
ui.add_galley(pos, galley, self.text_style, self.text_color);
}
} }
/// Usage: label!("Foo: {}", bar) /// Usage: label!("Foo: {}", bar)
@ -94,14 +107,9 @@ macro_rules! label {
impl Widget for Label { impl Widget for Label {
fn ui(self, ui: &mut Ui) -> GuiResponse { fn ui(self, ui: &mut Ui) -> GuiResponse {
let max_width = if self.auto_shrink { let galley = self.layout(ui);
ui.available_finite().width()
} else {
ui.available().width()
};
let galley = self.layout(max_width, ui);
let interact = ui.reserve_space(galley.size, None); let interact = ui.reserve_space(galley.size, None);
ui.add_galley(interact.rect.min, galley, self.text_style, self.text_color); self.paint_galley(ui, interact.rect.min, galley);
ui.response(interact) ui.response(interact)
} }
} }