Add culling of the painting for most widgets
This is a good early-out for widgets in `ScrollAreas`, but also prepares for speeding up the first pass of a possible two-pass version of egui: https://github.com/emilk/egui/issues/843
This commit is contained in:
parent
461f380a24
commit
eda1d91654
17 changed files with 525 additions and 462 deletions
|
@ -295,46 +295,48 @@ impl CollapsingHeader {
|
||||||
header_response
|
header_response
|
||||||
.widget_info(|| WidgetInfo::labeled(WidgetType::CollapsingHeader, text.text()));
|
.widget_info(|| WidgetInfo::labeled(WidgetType::CollapsingHeader, text.text()));
|
||||||
|
|
||||||
let visuals = ui
|
if ui.is_rect_visible(rect) {
|
||||||
.style()
|
let visuals = ui
|
||||||
.interact_selectable(&header_response, self.selected);
|
.style()
|
||||||
|
.interact_selectable(&header_response, self.selected);
|
||||||
|
|
||||||
if ui.visuals().collapsing_header_frame || self.show_background {
|
if ui.visuals().collapsing_header_frame || self.show_background {
|
||||||
ui.painter().add(epaint::RectShape {
|
ui.painter().add(epaint::RectShape {
|
||||||
rect: header_response.rect.expand(visuals.expansion),
|
rect: header_response.rect.expand(visuals.expansion),
|
||||||
corner_radius: visuals.corner_radius,
|
corner_radius: visuals.corner_radius,
|
||||||
fill: visuals.bg_fill,
|
fill: visuals.bg_fill,
|
||||||
stroke: visuals.bg_stroke,
|
stroke: visuals.bg_stroke,
|
||||||
// stroke: Default::default(),
|
// stroke: Default::default(),
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.selected
|
||||||
|
|| self.selectable && (header_response.hovered() || header_response.has_focus())
|
||||||
|
{
|
||||||
|
let rect = rect.expand(visuals.expansion);
|
||||||
|
|
||||||
|
let corner_radius = 2.0;
|
||||||
|
ui.painter()
|
||||||
|
.rect(rect, corner_radius, visuals.bg_fill, visuals.bg_stroke);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let (mut icon_rect, _) = ui.spacing().icon_rectangles(header_response.rect);
|
||||||
|
icon_rect.set_center(pos2(
|
||||||
|
header_response.rect.left() + ui.spacing().indent / 2.0,
|
||||||
|
header_response.rect.center().y,
|
||||||
|
));
|
||||||
|
let icon_response = Response {
|
||||||
|
rect: icon_rect,
|
||||||
|
..header_response.clone()
|
||||||
|
};
|
||||||
|
let openness = state.openness(ui.ctx(), id);
|
||||||
|
paint_icon(ui, openness, &icon_response);
|
||||||
|
}
|
||||||
|
|
||||||
|
text.paint_with_visuals(ui.painter(), text_pos, &visuals);
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.selected
|
|
||||||
|| self.selectable && (header_response.hovered() || header_response.has_focus())
|
|
||||||
{
|
|
||||||
let rect = rect.expand(visuals.expansion);
|
|
||||||
|
|
||||||
let corner_radius = 2.0;
|
|
||||||
ui.painter()
|
|
||||||
.rect(rect, corner_radius, visuals.bg_fill, visuals.bg_stroke);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
let (mut icon_rect, _) = ui.spacing().icon_rectangles(header_response.rect);
|
|
||||||
icon_rect.set_center(pos2(
|
|
||||||
header_response.rect.left() + ui.spacing().indent / 2.0,
|
|
||||||
header_response.rect.center().y,
|
|
||||||
));
|
|
||||||
let icon_response = Response {
|
|
||||||
rect: icon_rect,
|
|
||||||
..header_response.clone()
|
|
||||||
};
|
|
||||||
let openness = state.openness(ui.ctx(), id);
|
|
||||||
paint_icon(ui, openness, &icon_response);
|
|
||||||
}
|
|
||||||
|
|
||||||
text.paint_with_visuals(ui.painter(), text_pos, &visuals);
|
|
||||||
|
|
||||||
Prepared {
|
Prepared {
|
||||||
id,
|
id,
|
||||||
header_response,
|
header_response,
|
||||||
|
|
|
@ -173,16 +173,18 @@ fn combo_box_dyn<'c, R>(
|
||||||
let response = ui.interact(button_rect, button_id, Sense::click());
|
let response = ui.interact(button_rect, button_id, Sense::click());
|
||||||
// response.active |= is_popup_open;
|
// response.active |= is_popup_open;
|
||||||
|
|
||||||
let icon_rect = Align2::RIGHT_CENTER.align_size_within_rect(icon_size, rect);
|
if ui.is_rect_visible(rect) {
|
||||||
let visuals = if is_popup_open {
|
let icon_rect = Align2::RIGHT_CENTER.align_size_within_rect(icon_size, rect);
|
||||||
&ui.visuals().widgets.open
|
let visuals = if is_popup_open {
|
||||||
} else {
|
&ui.visuals().widgets.open
|
||||||
ui.style().interact(&response)
|
} else {
|
||||||
};
|
ui.style().interact(&response)
|
||||||
paint_icon(ui.painter(), icon_rect.expand(visuals.expansion), visuals);
|
};
|
||||||
|
paint_icon(ui.painter(), icon_rect.expand(visuals.expansion), visuals);
|
||||||
|
|
||||||
let text_rect = Align2::LEFT_CENTER.align_size_within_rect(galley.size(), rect);
|
let text_rect = Align2::LEFT_CENTER.align_size_within_rect(galley.size(), rect);
|
||||||
galley.paint_with_visuals(ui.painter(), text_rect.min, visuals);
|
galley.paint_with_visuals(ui.painter(), text_rect.min, visuals);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if button_response.clicked() {
|
if button_response.clicked() {
|
||||||
|
@ -223,21 +225,24 @@ fn button_frame(
|
||||||
outer_rect.set_height(outer_rect.height().at_least(interact_size.y));
|
outer_rect.set_height(outer_rect.height().at_least(interact_size.y));
|
||||||
|
|
||||||
let response = ui.interact(outer_rect, id, sense);
|
let response = ui.interact(outer_rect, id, sense);
|
||||||
let visuals = if is_popup_open {
|
|
||||||
&ui.visuals().widgets.open
|
|
||||||
} else {
|
|
||||||
ui.style().interact(&response)
|
|
||||||
};
|
|
||||||
|
|
||||||
ui.painter().set(
|
if ui.is_rect_visible(outer_rect) {
|
||||||
where_to_put_background,
|
let visuals = if is_popup_open {
|
||||||
epaint::RectShape {
|
&ui.visuals().widgets.open
|
||||||
rect: outer_rect.expand(visuals.expansion),
|
} else {
|
||||||
corner_radius: visuals.corner_radius,
|
ui.style().interact(&response)
|
||||||
fill: visuals.bg_fill,
|
};
|
||||||
stroke: visuals.bg_stroke,
|
|
||||||
},
|
ui.painter().set(
|
||||||
);
|
where_to_put_background,
|
||||||
|
epaint::RectShape {
|
||||||
|
rect: outer_rect.expand(visuals.expansion),
|
||||||
|
corner_radius: visuals.corner_radius,
|
||||||
|
fill: visuals.bg_fill,
|
||||||
|
stroke: visuals.bg_stroke,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
ui.advance_cursor_after_rect(outer_rect);
|
ui.advance_cursor_after_rect(outer_rect);
|
||||||
|
|
||||||
|
|
|
@ -209,8 +209,11 @@ impl Prepared {
|
||||||
..
|
..
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
let shape = frame.paint(outer_rect);
|
if ui.is_rect_visible(outer_rect) {
|
||||||
ui.painter().set(where_to_put_background, shape);
|
let shape = frame.paint(outer_rect);
|
||||||
|
ui.painter().set(where_to_put_background, shape);
|
||||||
|
}
|
||||||
|
|
||||||
ui.allocate_rect(outer_rect, Sense::hover())
|
ui.allocate_rect(outer_rect, Sense::hover())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -671,50 +671,52 @@ impl Prepared {
|
||||||
state.vel[d] = 0.0;
|
state.vel[d] = 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Avoid frame-delay by calculating a new handle rect:
|
if ui.is_rect_visible(outer_scroll_rect) {
|
||||||
let mut handle_rect = if d == 0 {
|
// Avoid frame-delay by calculating a new handle rect:
|
||||||
Rect::from_min_max(
|
let mut handle_rect = if d == 0 {
|
||||||
pos2(from_content(state.offset.x), min_cross),
|
Rect::from_min_max(
|
||||||
pos2(from_content(state.offset.x + inner_rect.width()), max_cross),
|
pos2(from_content(state.offset.x), min_cross),
|
||||||
)
|
pos2(from_content(state.offset.x + inner_rect.width()), max_cross),
|
||||||
} else {
|
)
|
||||||
Rect::from_min_max(
|
} else {
|
||||||
pos2(min_cross, from_content(state.offset.y)),
|
Rect::from_min_max(
|
||||||
pos2(
|
pos2(min_cross, from_content(state.offset.y)),
|
||||||
max_cross,
|
pos2(
|
||||||
from_content(state.offset.y + inner_rect.height()),
|
max_cross,
|
||||||
),
|
from_content(state.offset.y + inner_rect.height()),
|
||||||
)
|
),
|
||||||
};
|
)
|
||||||
let min_handle_size = ui.spacing().scroll_bar_width;
|
};
|
||||||
if handle_rect.size()[d] < min_handle_size {
|
let min_handle_size = ui.spacing().scroll_bar_width;
|
||||||
handle_rect = Rect::from_center_size(
|
if handle_rect.size()[d] < min_handle_size {
|
||||||
handle_rect.center(),
|
handle_rect = Rect::from_center_size(
|
||||||
if d == 0 {
|
handle_rect.center(),
|
||||||
vec2(min_handle_size, handle_rect.size().y)
|
if d == 0 {
|
||||||
} else {
|
vec2(min_handle_size, handle_rect.size().y)
|
||||||
vec2(handle_rect.size().x, min_handle_size)
|
} else {
|
||||||
},
|
vec2(handle_rect.size().x, min_handle_size)
|
||||||
);
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let visuals = if scrolling_enabled {
|
||||||
|
ui.style().interact(&response)
|
||||||
|
} else {
|
||||||
|
&ui.style().visuals.widgets.inactive
|
||||||
|
};
|
||||||
|
|
||||||
|
ui.painter().add(epaint::Shape::rect_filled(
|
||||||
|
outer_scroll_rect,
|
||||||
|
visuals.corner_radius,
|
||||||
|
ui.visuals().extreme_bg_color,
|
||||||
|
));
|
||||||
|
|
||||||
|
ui.painter().add(epaint::Shape::rect_filled(
|
||||||
|
handle_rect,
|
||||||
|
visuals.corner_radius,
|
||||||
|
visuals.bg_fill,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let visuals = if scrolling_enabled {
|
|
||||||
ui.style().interact(&response)
|
|
||||||
} else {
|
|
||||||
&ui.style().visuals.widgets.inactive
|
|
||||||
};
|
|
||||||
|
|
||||||
ui.painter().add(epaint::Shape::rect_filled(
|
|
||||||
outer_scroll_rect,
|
|
||||||
visuals.corner_radius,
|
|
||||||
ui.visuals().extreme_bg_color,
|
|
||||||
));
|
|
||||||
|
|
||||||
ui.painter().add(epaint::Shape::rect_filled(
|
|
||||||
handle_rect,
|
|
||||||
visuals.corner_radius,
|
|
||||||
visuals.bg_fill,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ui.advance_cursor_after_rect(outer_rect);
|
ui.advance_cursor_after_rect(outer_rect);
|
||||||
|
|
|
@ -426,7 +426,7 @@ impl SubMenuButton {
|
||||||
crate::WidgetInfo::labeled(crate::WidgetType::Button, &text_galley.text())
|
crate::WidgetInfo::labeled(crate::WidgetType::Button, &text_galley.text())
|
||||||
});
|
});
|
||||||
|
|
||||||
if ui.clip_rect().intersects(rect) {
|
if ui.is_rect_visible(rect) {
|
||||||
let visuals = Self::visuals(ui, &response, menu_state, sub_id);
|
let visuals = Self::visuals(ui, &response, menu_state, sub_id);
|
||||||
let text_pos = Align2::LEFT_CENTER
|
let text_pos = Align2::LEFT_CENTER
|
||||||
.align_size_within_rect(text_galley.size(), rect.shrink2(button_padding))
|
.align_size_within_rect(text_galley.size(), rect.shrink2(button_padding))
|
||||||
|
|
|
@ -122,13 +122,13 @@ impl Ui {
|
||||||
// -------------------------------------------------
|
// -------------------------------------------------
|
||||||
|
|
||||||
/// A unique identity of this `Ui`.
|
/// A unique identity of this `Ui`.
|
||||||
#[inline(always)]
|
#[inline]
|
||||||
pub fn id(&self) -> Id {
|
pub fn id(&self) -> Id {
|
||||||
self.id
|
self.id
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Style options for this `Ui` and its children.
|
/// Style options for this `Ui` and its children.
|
||||||
#[inline(always)]
|
#[inline]
|
||||||
pub fn style(&self) -> &std::sync::Arc<Style> {
|
pub fn style(&self) -> &std::sync::Arc<Style> {
|
||||||
&self.style
|
&self.style
|
||||||
}
|
}
|
||||||
|
@ -161,7 +161,7 @@ impl Ui {
|
||||||
|
|
||||||
/// The current spacing options for this `Ui`.
|
/// The current spacing options for this `Ui`.
|
||||||
/// Short for `ui.style().spacing`.
|
/// Short for `ui.style().spacing`.
|
||||||
#[inline(always)]
|
#[inline]
|
||||||
pub fn spacing(&self) -> &crate::style::Spacing {
|
pub fn spacing(&self) -> &crate::style::Spacing {
|
||||||
&self.style.spacing
|
&self.style.spacing
|
||||||
}
|
}
|
||||||
|
@ -180,7 +180,7 @@ impl Ui {
|
||||||
|
|
||||||
/// The current visuals settings of this `Ui`.
|
/// The current visuals settings of this `Ui`.
|
||||||
/// Short for `ui.style().visuals`.
|
/// Short for `ui.style().visuals`.
|
||||||
#[inline(always)]
|
#[inline]
|
||||||
pub fn visuals(&self) -> &crate::Visuals {
|
pub fn visuals(&self) -> &crate::Visuals {
|
||||||
&self.style.visuals
|
&self.style.visuals
|
||||||
}
|
}
|
||||||
|
@ -200,20 +200,20 @@ impl Ui {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a reference to the parent [`CtxRef`].
|
/// Get a reference to the parent [`CtxRef`].
|
||||||
#[inline(always)]
|
#[inline]
|
||||||
pub fn ctx(&self) -> &CtxRef {
|
pub fn ctx(&self) -> &CtxRef {
|
||||||
self.painter.ctx()
|
self.painter.ctx()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Use this to paint stuff within this `Ui`.
|
/// Use this to paint stuff within this `Ui`.
|
||||||
#[inline(always)]
|
#[inline]
|
||||||
pub fn painter(&self) -> &Painter {
|
pub fn painter(&self) -> &Painter {
|
||||||
&self.painter
|
&self.painter
|
||||||
}
|
}
|
||||||
|
|
||||||
/// If `false`, the `Ui` does not allow any interaction and
|
/// If `false`, the `Ui` does not allow any interaction and
|
||||||
/// the widgets in it will draw with a gray look.
|
/// the widgets in it will draw with a gray look.
|
||||||
#[inline(always)]
|
#[inline]
|
||||||
pub fn is_enabled(&self) -> bool {
|
pub fn is_enabled(&self) -> bool {
|
||||||
self.enabled
|
self.enabled
|
||||||
}
|
}
|
||||||
|
@ -246,7 +246,7 @@ impl Ui {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// If `false`, any widgets added to the `Ui` will be invisible and non-interactive.
|
/// If `false`, any widgets added to the `Ui` will be invisible and non-interactive.
|
||||||
#[inline(always)]
|
#[inline]
|
||||||
pub fn visible(&self) -> bool {
|
pub fn visible(&self) -> bool {
|
||||||
self.painter.visible()
|
self.painter.visible()
|
||||||
}
|
}
|
||||||
|
@ -277,7 +277,7 @@ impl Ui {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline]
|
||||||
pub fn layout(&self) -> &Layout {
|
pub fn layout(&self) -> &Layout {
|
||||||
self.placer.layout()
|
self.placer.layout()
|
||||||
}
|
}
|
||||||
|
@ -305,37 +305,42 @@ impl Ui {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Use this to paint stuff within this `Ui`.
|
/// Use this to paint stuff within this `Ui`.
|
||||||
|
#[inline]
|
||||||
pub fn layer_id(&self) -> LayerId {
|
pub fn layer_id(&self) -> LayerId {
|
||||||
self.painter().layer_id()
|
self.painter().layer_id()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The `Input` of the `Context` associated with the `Ui`.
|
/// The `Input` of the `Context` associated with the `Ui`.
|
||||||
/// Equivalent to `.ctx().input()`.
|
/// Equivalent to `.ctx().input()`.
|
||||||
#[inline(always)]
|
#[inline]
|
||||||
pub fn input(&self) -> &InputState {
|
pub fn input(&self) -> &InputState {
|
||||||
self.ctx().input()
|
self.ctx().input()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The `Memory` of the `Context` associated with the `Ui`.
|
/// The `Memory` of the `Context` associated with the `Ui`.
|
||||||
/// Equivalent to `.ctx().memory()`.
|
/// Equivalent to `.ctx().memory()`.
|
||||||
|
#[inline]
|
||||||
pub fn memory(&self) -> MutexGuard<'_, Memory> {
|
pub fn memory(&self) -> MutexGuard<'_, Memory> {
|
||||||
self.ctx().memory()
|
self.ctx().memory()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The `Output` of the `Context` associated with the `Ui`.
|
/// The `Output` of the `Context` associated with the `Ui`.
|
||||||
/// Equivalent to `.ctx().output()`.
|
/// Equivalent to `.ctx().output()`.
|
||||||
|
#[inline]
|
||||||
pub fn output(&self) -> MutexGuard<'_, Output> {
|
pub fn output(&self) -> MutexGuard<'_, Output> {
|
||||||
self.ctx().output()
|
self.ctx().output()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The `Fonts` of the `Context` associated with the `Ui`.
|
/// The `Fonts` of the `Context` associated with the `Ui`.
|
||||||
/// Equivalent to `.ctx().fonts()`.
|
/// Equivalent to `.ctx().fonts()`.
|
||||||
|
#[inline]
|
||||||
pub fn fonts(&self) -> &Fonts {
|
pub fn fonts(&self) -> &Fonts {
|
||||||
self.ctx().fonts()
|
self.ctx().fonts()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Screen-space rectangle for clipping what we paint in this ui.
|
/// Screen-space rectangle for clipping what we paint in this ui.
|
||||||
/// This is used, for instance, to avoid painting outside a window that is smaller than its contents.
|
/// This is used, for instance, to avoid painting outside a window that is smaller than its contents.
|
||||||
|
#[inline]
|
||||||
pub fn clip_rect(&self) -> Rect {
|
pub fn clip_rect(&self) -> Rect {
|
||||||
self.painter.clip_rect()
|
self.painter.clip_rect()
|
||||||
}
|
}
|
||||||
|
@ -345,6 +350,11 @@ impl Ui {
|
||||||
pub fn set_clip_rect(&mut self, clip_rect: Rect) {
|
pub fn set_clip_rect(&mut self, clip_rect: Rect) {
|
||||||
self.painter.set_clip_rect(clip_rect);
|
self.painter.set_clip_rect(clip_rect);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Can be used for culling: if `false`, then no part of `rect` will be visible on screen.
|
||||||
|
pub fn is_rect_visible(&self, rect: Rect) -> bool {
|
||||||
|
self.visible() && rect.intersects(self.clip_rect())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
|
@ -741,7 +751,7 @@ impl Ui {
|
||||||
/// If the contents overflow, more space will be allocated.
|
/// If the contents overflow, more space will be allocated.
|
||||||
/// When finished, the amount of space actually used (`min_rect`) will be allocated.
|
/// When finished, the amount of space actually used (`min_rect`) will be allocated.
|
||||||
/// So you can request a lot of space and then use less.
|
/// So you can request a lot of space and then use less.
|
||||||
#[inline(always)]
|
#[inline]
|
||||||
pub fn allocate_ui<R>(
|
pub fn allocate_ui<R>(
|
||||||
&mut self,
|
&mut self,
|
||||||
desired_size: Vec2,
|
desired_size: Vec2,
|
||||||
|
@ -754,7 +764,7 @@ impl Ui {
|
||||||
/// If the contents overflow, more space will be allocated.
|
/// If the contents overflow, more space will be allocated.
|
||||||
/// When finished, the amount of space actually used (`min_rect`) will be allocated.
|
/// When finished, the amount of space actually used (`min_rect`) will be allocated.
|
||||||
/// So you can request a lot of space and then use less.
|
/// So you can request a lot of space and then use less.
|
||||||
#[inline(always)]
|
#[inline]
|
||||||
pub fn allocate_ui_with_layout<R>(
|
pub fn allocate_ui_with_layout<R>(
|
||||||
&mut self,
|
&mut self,
|
||||||
desired_size: Vec2,
|
desired_size: Vec2,
|
||||||
|
@ -883,7 +893,7 @@ impl Ui {
|
||||||
/// let response = ui.add(egui::Slider::new(&mut my_value, 0..=100));
|
/// let response = ui.add(egui::Slider::new(&mut my_value, 0..=100));
|
||||||
/// response.on_hover_text("Drag me!");
|
/// response.on_hover_text("Drag me!");
|
||||||
/// ```
|
/// ```
|
||||||
#[inline(always)]
|
#[inline]
|
||||||
pub fn add(&mut self, widget: impl Widget) -> Response {
|
pub fn add(&mut self, widget: impl Widget) -> Response {
|
||||||
widget.ui(self)
|
widget.ui(self)
|
||||||
}
|
}
|
||||||
|
@ -980,7 +990,7 @@ impl Ui {
|
||||||
/// This will be in addition to the [`Spacing::item_spacing`}.
|
/// This will be in addition to the [`Spacing::item_spacing`}.
|
||||||
///
|
///
|
||||||
/// [`Self::min_rect`] will expand to contain the space.
|
/// [`Self::min_rect`] will expand to contain the space.
|
||||||
#[inline(always)]
|
#[inline]
|
||||||
pub fn add_space(&mut self, amount: f32) {
|
pub fn add_space(&mut self, amount: f32) {
|
||||||
self.placer.advance_cursor(amount);
|
self.placer.advance_cursor(amount);
|
||||||
}
|
}
|
||||||
|
@ -990,7 +1000,7 @@ impl Ui {
|
||||||
/// Shortcut for `add(Label::new(text))`
|
/// Shortcut for `add(Label::new(text))`
|
||||||
///
|
///
|
||||||
/// See also [`Label`].
|
/// See also [`Label`].
|
||||||
#[inline(always)]
|
#[inline]
|
||||||
pub fn label(&mut self, text: impl Into<WidgetText>) -> Response {
|
pub fn label(&mut self, text: impl Into<WidgetText>) -> Response {
|
||||||
Label::new(text).ui(self)
|
Label::new(text).ui(self)
|
||||||
}
|
}
|
||||||
|
@ -1197,7 +1207,7 @@ impl Ui {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Shortcut for `add(Separator::default())` (see [`Separator`]).
|
/// Shortcut for `add(Separator::default())` (see [`Separator`]).
|
||||||
#[inline(always)]
|
#[inline]
|
||||||
pub fn separator(&mut self) -> Response {
|
pub fn separator(&mut self) -> Response {
|
||||||
Separator::default().ui(self)
|
Separator::default().ui(self)
|
||||||
}
|
}
|
||||||
|
@ -1243,7 +1253,7 @@ impl Ui {
|
||||||
/// Show an image here with the given size.
|
/// Show an image here with the given size.
|
||||||
///
|
///
|
||||||
/// See also [`Image`].
|
/// See also [`Image`].
|
||||||
#[inline(always)]
|
#[inline]
|
||||||
pub fn image(&mut self, texture_id: TextureId, size: impl Into<Vec2>) -> Response {
|
pub fn image(&mut self, texture_id: TextureId, size: impl Into<Vec2>) -> Response {
|
||||||
Image::new(texture_id, size).ui(self)
|
Image::new(texture_id, size).ui(self)
|
||||||
}
|
}
|
||||||
|
@ -1403,7 +1413,7 @@ impl Ui {
|
||||||
///
|
///
|
||||||
/// The `id_source` here be anything at all.
|
/// The `id_source` here be anything at all.
|
||||||
// TODO: remove `id_source` argument?
|
// TODO: remove `id_source` argument?
|
||||||
#[inline(always)]
|
#[inline]
|
||||||
pub fn indent<R>(
|
pub fn indent<R>(
|
||||||
&mut self,
|
&mut self,
|
||||||
id_source: impl Hash,
|
id_source: impl Hash,
|
||||||
|
@ -1555,7 +1565,7 @@ impl Ui {
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// See also [`Self::with_layout`] for more options.
|
/// See also [`Self::with_layout`] for more options.
|
||||||
#[inline(always)]
|
#[inline]
|
||||||
pub fn vertical<R>(&mut self, add_contents: impl FnOnce(&mut Ui) -> R) -> InnerResponse<R> {
|
pub fn vertical<R>(&mut self, add_contents: impl FnOnce(&mut Ui) -> R) -> InnerResponse<R> {
|
||||||
self.with_layout_dyn(Layout::top_down(Align::Min), Box::new(add_contents))
|
self.with_layout_dyn(Layout::top_down(Align::Min), Box::new(add_contents))
|
||||||
}
|
}
|
||||||
|
@ -1750,7 +1760,7 @@ impl Ui {
|
||||||
self.menu_state = menu_state;
|
self.menu_state = menu_state;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline]
|
||||||
/// Create a menu button. Creates a button for a sub-menu when the `Ui` is inside a menu.
|
/// Create a menu button. Creates a button for a sub-menu when the `Ui` is inside a menu.
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
|
|
|
@ -144,7 +144,7 @@ impl Widget for Button {
|
||||||
let (rect, response) = ui.allocate_at_least(desired_size, sense);
|
let (rect, response) = ui.allocate_at_least(desired_size, sense);
|
||||||
response.widget_info(|| WidgetInfo::labeled(WidgetType::Button, text.text()));
|
response.widget_info(|| WidgetInfo::labeled(WidgetType::Button, text.text()));
|
||||||
|
|
||||||
if ui.clip_rect().intersects(rect) {
|
if ui.is_rect_visible(rect) {
|
||||||
let visuals = ui.style().interact(&response);
|
let visuals = ui.style().interact(&response);
|
||||||
let text_pos = ui
|
let text_pos = ui
|
||||||
.layout()
|
.layout()
|
||||||
|
@ -234,33 +234,36 @@ impl<'a> Widget for Checkbox<'a> {
|
||||||
}
|
}
|
||||||
response.widget_info(|| WidgetInfo::selected(WidgetType::Checkbox, *checked, text.text()));
|
response.widget_info(|| WidgetInfo::selected(WidgetType::Checkbox, *checked, text.text()));
|
||||||
|
|
||||||
// let visuals = ui.style().interact_selectable(&response, *checked); // too colorful
|
if ui.is_rect_visible(rect) {
|
||||||
let visuals = ui.style().interact(&response);
|
// let visuals = ui.style().interact_selectable(&response, *checked); // too colorful
|
||||||
let text_pos = pos2(
|
let visuals = ui.style().interact(&response);
|
||||||
rect.min.x + button_padding.x + icon_width + icon_spacing,
|
let text_pos = pos2(
|
||||||
rect.center().y - 0.5 * text.size().y,
|
rect.min.x + button_padding.x + icon_width + icon_spacing,
|
||||||
);
|
rect.center().y - 0.5 * text.size().y,
|
||||||
let (small_icon_rect, big_icon_rect) = ui.spacing().icon_rectangles(rect);
|
);
|
||||||
ui.painter().add(epaint::RectShape {
|
let (small_icon_rect, big_icon_rect) = ui.spacing().icon_rectangles(rect);
|
||||||
rect: big_icon_rect.expand(visuals.expansion),
|
ui.painter().add(epaint::RectShape {
|
||||||
corner_radius: visuals.corner_radius,
|
rect: big_icon_rect.expand(visuals.expansion),
|
||||||
fill: visuals.bg_fill,
|
corner_radius: visuals.corner_radius,
|
||||||
stroke: visuals.bg_stroke,
|
fill: visuals.bg_fill,
|
||||||
});
|
stroke: visuals.bg_stroke,
|
||||||
|
});
|
||||||
|
|
||||||
if *checked {
|
if *checked {
|
||||||
// Check mark:
|
// Check mark:
|
||||||
ui.painter().add(Shape::line(
|
ui.painter().add(Shape::line(
|
||||||
vec![
|
vec![
|
||||||
pos2(small_icon_rect.left(), small_icon_rect.center().y),
|
pos2(small_icon_rect.left(), small_icon_rect.center().y),
|
||||||
pos2(small_icon_rect.center().x, small_icon_rect.bottom()),
|
pos2(small_icon_rect.center().x, small_icon_rect.bottom()),
|
||||||
pos2(small_icon_rect.right(), small_icon_rect.top()),
|
pos2(small_icon_rect.right(), small_icon_rect.top()),
|
||||||
],
|
],
|
||||||
visuals.fg_stroke,
|
visuals.fg_stroke,
|
||||||
));
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
text.paint_with_visuals(ui.painter(), text_pos, visuals);
|
||||||
}
|
}
|
||||||
|
|
||||||
text.paint_with_visuals(ui.painter(), text_pos, visuals);
|
|
||||||
response
|
response
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -331,36 +334,39 @@ impl Widget for RadioButton {
|
||||||
response
|
response
|
||||||
.widget_info(|| WidgetInfo::selected(WidgetType::RadioButton, checked, text.text()));
|
.widget_info(|| WidgetInfo::selected(WidgetType::RadioButton, checked, text.text()));
|
||||||
|
|
||||||
let text_pos = pos2(
|
if ui.is_rect_visible(rect) {
|
||||||
rect.min.x + button_padding.x + icon_width + icon_spacing,
|
let text_pos = pos2(
|
||||||
rect.center().y - 0.5 * text.size().y,
|
rect.min.x + button_padding.x + icon_width + icon_spacing,
|
||||||
);
|
rect.center().y - 0.5 * text.size().y,
|
||||||
|
);
|
||||||
|
|
||||||
// let visuals = ui.style().interact_selectable(&response, checked); // too colorful
|
// let visuals = ui.style().interact_selectable(&response, checked); // too colorful
|
||||||
let visuals = ui.style().interact(&response);
|
let visuals = ui.style().interact(&response);
|
||||||
|
|
||||||
let (small_icon_rect, big_icon_rect) = ui.spacing().icon_rectangles(rect);
|
let (small_icon_rect, big_icon_rect) = ui.spacing().icon_rectangles(rect);
|
||||||
|
|
||||||
let painter = ui.painter();
|
let painter = ui.painter();
|
||||||
|
|
||||||
painter.add(epaint::CircleShape {
|
|
||||||
center: big_icon_rect.center(),
|
|
||||||
radius: big_icon_rect.width() / 2.0 + visuals.expansion,
|
|
||||||
fill: visuals.bg_fill,
|
|
||||||
stroke: visuals.bg_stroke,
|
|
||||||
});
|
|
||||||
|
|
||||||
if checked {
|
|
||||||
painter.add(epaint::CircleShape {
|
painter.add(epaint::CircleShape {
|
||||||
center: small_icon_rect.center(),
|
center: big_icon_rect.center(),
|
||||||
radius: small_icon_rect.width() / 3.0,
|
radius: big_icon_rect.width() / 2.0 + visuals.expansion,
|
||||||
fill: visuals.fg_stroke.color, // Intentional to use stroke and not fill
|
fill: visuals.bg_fill,
|
||||||
// fill: ui.visuals().selection.stroke.color, // too much color
|
stroke: visuals.bg_stroke,
|
||||||
stroke: Default::default(),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if checked {
|
||||||
|
painter.add(epaint::CircleShape {
|
||||||
|
center: small_icon_rect.center(),
|
||||||
|
radius: small_icon_rect.width() / 3.0,
|
||||||
|
fill: visuals.fg_stroke.color, // Intentional to use stroke and not fill
|
||||||
|
// fill: ui.visuals().selection.stroke.color, // too much color
|
||||||
|
stroke: Default::default(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
text.paint_with_visuals(ui.painter(), text_pos, visuals);
|
||||||
}
|
}
|
||||||
|
|
||||||
text.paint_with_visuals(ui.painter(), text_pos, visuals);
|
|
||||||
response
|
response
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -438,7 +444,7 @@ impl Widget for ImageButton {
|
||||||
let (rect, response) = ui.allocate_exact_size(padded_size, sense);
|
let (rect, response) = ui.allocate_exact_size(padded_size, sense);
|
||||||
response.widget_info(|| WidgetInfo::new(WidgetType::ImageButton));
|
response.widget_info(|| WidgetInfo::new(WidgetType::ImageButton));
|
||||||
|
|
||||||
if ui.clip_rect().intersects(rect) {
|
if ui.is_rect_visible(rect) {
|
||||||
let (expansion, corner_radius, fill, stroke) = if selected {
|
let (expansion, corner_radius, fill, stroke) = if selected {
|
||||||
let selection = ui.visuals().selection;
|
let selection = ui.visuals().selection;
|
||||||
(-padding, 0.0, selection.bg_fill, selection.stroke)
|
(-padding, 0.0, selection.bg_fill, selection.stroke)
|
||||||
|
|
|
@ -50,19 +50,21 @@ pub fn show_color(ui: &mut Ui, color: impl Into<Hsva>, desired_size: Vec2) -> Re
|
||||||
|
|
||||||
fn show_hsva(ui: &mut Ui, color: Hsva, desired_size: Vec2) -> Response {
|
fn show_hsva(ui: &mut Ui, color: Hsva, desired_size: Vec2) -> Response {
|
||||||
let (rect, response) = ui.allocate_at_least(desired_size, Sense::hover());
|
let (rect, response) = ui.allocate_at_least(desired_size, Sense::hover());
|
||||||
background_checkers(ui.painter(), rect);
|
if ui.is_rect_visible(rect) {
|
||||||
if true {
|
background_checkers(ui.painter(), rect);
|
||||||
let left = Rect::from_min_max(rect.left_top(), rect.center_bottom());
|
if true {
|
||||||
let right = Rect::from_min_max(rect.center_top(), rect.right_bottom());
|
let left = Rect::from_min_max(rect.left_top(), rect.center_bottom());
|
||||||
ui.painter().rect_filled(left, 0.0, color);
|
let right = Rect::from_min_max(rect.center_top(), rect.right_bottom());
|
||||||
ui.painter().rect_filled(right, 0.0, color.to_opaque());
|
ui.painter().rect_filled(left, 0.0, color);
|
||||||
} else {
|
ui.painter().rect_filled(right, 0.0, color.to_opaque());
|
||||||
ui.painter().add(RectShape {
|
} else {
|
||||||
rect,
|
ui.painter().add(RectShape {
|
||||||
corner_radius: 2.0,
|
rect,
|
||||||
fill: color.into(),
|
corner_radius: 2.0,
|
||||||
stroke: Stroke::new(3.0, color.to_opaque()),
|
fill: color.into(),
|
||||||
});
|
stroke: Stroke::new(3.0, color.to_opaque()),
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
response
|
response
|
||||||
}
|
}
|
||||||
|
@ -71,23 +73,26 @@ fn color_button(ui: &mut Ui, color: Color32, open: bool) -> Response {
|
||||||
let size = ui.spacing().interact_size;
|
let size = ui.spacing().interact_size;
|
||||||
let (rect, response) = ui.allocate_exact_size(size, Sense::click());
|
let (rect, response) = ui.allocate_exact_size(size, Sense::click());
|
||||||
response.widget_info(|| WidgetInfo::new(WidgetType::ColorButton));
|
response.widget_info(|| WidgetInfo::new(WidgetType::ColorButton));
|
||||||
let visuals = if open {
|
|
||||||
&ui.visuals().widgets.open
|
|
||||||
} else {
|
|
||||||
ui.style().interact(&response)
|
|
||||||
};
|
|
||||||
let rect = rect.expand(visuals.expansion);
|
|
||||||
|
|
||||||
background_checkers(ui.painter(), rect);
|
if ui.is_rect_visible(rect) {
|
||||||
|
let visuals = if open {
|
||||||
|
&ui.visuals().widgets.open
|
||||||
|
} else {
|
||||||
|
ui.style().interact(&response)
|
||||||
|
};
|
||||||
|
let rect = rect.expand(visuals.expansion);
|
||||||
|
|
||||||
let left_half = Rect::from_min_max(rect.left_top(), rect.center_bottom());
|
background_checkers(ui.painter(), rect);
|
||||||
let right_half = Rect::from_min_max(rect.center_top(), rect.right_bottom());
|
|
||||||
ui.painter().rect_filled(left_half, 0.0, color);
|
|
||||||
ui.painter().rect_filled(right_half, 0.0, color.to_opaque());
|
|
||||||
|
|
||||||
let corner_radius = visuals.corner_radius.at_most(2.0);
|
let left_half = Rect::from_min_max(rect.left_top(), rect.center_bottom());
|
||||||
ui.painter()
|
let right_half = Rect::from_min_max(rect.center_top(), rect.right_bottom());
|
||||||
.rect_stroke(rect, corner_radius, (2.0, visuals.bg_fill)); // fill is intentional, because default style has no border
|
ui.painter().rect_filled(left_half, 0.0, color);
|
||||||
|
ui.painter().rect_filled(right_half, 0.0, color.to_opaque());
|
||||||
|
|
||||||
|
let corner_radius = visuals.corner_radius.at_most(2.0);
|
||||||
|
ui.painter()
|
||||||
|
.rect_stroke(rect, corner_radius, (2.0, visuals.bg_fill)); // fill is intentional, because default style has no border
|
||||||
|
}
|
||||||
|
|
||||||
response
|
response
|
||||||
}
|
}
|
||||||
|
@ -102,43 +107,45 @@ fn color_slider_1d(ui: &mut Ui, value: &mut f32, color_at: impl Fn(f32) -> Color
|
||||||
*value = remap_clamp(mpos.x, rect.left()..=rect.right(), 0.0..=1.0);
|
*value = remap_clamp(mpos.x, rect.left()..=rect.right(), 0.0..=1.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
let visuals = ui.style().interact(&response);
|
if ui.is_rect_visible(rect) {
|
||||||
|
let visuals = ui.style().interact(&response);
|
||||||
|
|
||||||
background_checkers(ui.painter(), rect); // for alpha:
|
background_checkers(ui.painter(), rect); // for alpha:
|
||||||
|
|
||||||
{
|
{
|
||||||
// fill color:
|
// fill color:
|
||||||
let mut mesh = Mesh::default();
|
let mut mesh = Mesh::default();
|
||||||
for i in 0..=N {
|
for i in 0..=N {
|
||||||
let t = i as f32 / (N as f32);
|
let t = i as f32 / (N as f32);
|
||||||
let color = color_at(t);
|
let color = color_at(t);
|
||||||
let x = lerp(rect.left()..=rect.right(), t);
|
let x = lerp(rect.left()..=rect.right(), t);
|
||||||
mesh.colored_vertex(pos2(x, rect.top()), color);
|
mesh.colored_vertex(pos2(x, rect.top()), color);
|
||||||
mesh.colored_vertex(pos2(x, rect.bottom()), color);
|
mesh.colored_vertex(pos2(x, rect.bottom()), color);
|
||||||
if i < N {
|
if i < N {
|
||||||
mesh.add_triangle(2 * i + 0, 2 * i + 1, 2 * i + 2);
|
mesh.add_triangle(2 * i + 0, 2 * i + 1, 2 * i + 2);
|
||||||
mesh.add_triangle(2 * i + 1, 2 * i + 2, 2 * i + 3);
|
mesh.add_triangle(2 * i + 1, 2 * i + 2, 2 * i + 3);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
ui.painter().add(Shape::mesh(mesh));
|
||||||
}
|
}
|
||||||
ui.painter().add(Shape::mesh(mesh));
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.painter().rect_stroke(rect, 0.0, visuals.bg_stroke); // outline
|
ui.painter().rect_stroke(rect, 0.0, visuals.bg_stroke); // outline
|
||||||
|
|
||||||
{
|
{
|
||||||
// Show where the slider is at:
|
// Show where the slider is at:
|
||||||
let x = lerp(rect.left()..=rect.right(), *value);
|
let x = lerp(rect.left()..=rect.right(), *value);
|
||||||
let r = rect.height() / 4.0;
|
let r = rect.height() / 4.0;
|
||||||
let picked_color = color_at(*value);
|
let picked_color = color_at(*value);
|
||||||
ui.painter().add(Shape::convex_polygon(
|
ui.painter().add(Shape::convex_polygon(
|
||||||
vec![
|
vec![
|
||||||
pos2(x - r, rect.bottom()),
|
pos2(x - r, rect.bottom()),
|
||||||
pos2(x + r, rect.bottom()),
|
pos2(x + r, rect.bottom()),
|
||||||
pos2(x, rect.center().y),
|
pos2(x, rect.center().y),
|
||||||
],
|
],
|
||||||
picked_color,
|
picked_color,
|
||||||
Stroke::new(visuals.fg_stroke.width, contrast_color(picked_color)),
|
Stroke::new(visuals.fg_stroke.width, contrast_color(picked_color)),
|
||||||
));
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
response
|
response
|
||||||
|
@ -158,41 +165,43 @@ fn color_slider_2d(
|
||||||
*y_value = remap_clamp(mpos.y, rect.bottom()..=rect.top(), 0.0..=1.0);
|
*y_value = remap_clamp(mpos.y, rect.bottom()..=rect.top(), 0.0..=1.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
let visuals = ui.style().interact(&response);
|
if ui.is_rect_visible(rect) {
|
||||||
let mut mesh = Mesh::default();
|
let visuals = ui.style().interact(&response);
|
||||||
|
let mut mesh = Mesh::default();
|
||||||
|
|
||||||
for xi in 0..=N {
|
for xi in 0..=N {
|
||||||
for yi in 0..=N {
|
for yi in 0..=N {
|
||||||
let xt = xi as f32 / (N as f32);
|
let xt = xi as f32 / (N as f32);
|
||||||
let yt = yi as f32 / (N as f32);
|
let yt = yi as f32 / (N as f32);
|
||||||
let color = color_at(xt, yt);
|
let color = color_at(xt, yt);
|
||||||
let x = lerp(rect.left()..=rect.right(), xt);
|
let x = lerp(rect.left()..=rect.right(), xt);
|
||||||
let y = lerp(rect.bottom()..=rect.top(), yt);
|
let y = lerp(rect.bottom()..=rect.top(), yt);
|
||||||
mesh.colored_vertex(pos2(x, y), color);
|
mesh.colored_vertex(pos2(x, y), color);
|
||||||
|
|
||||||
if xi < N && yi < N {
|
if xi < N && yi < N {
|
||||||
let x_offset = 1;
|
let x_offset = 1;
|
||||||
let y_offset = N + 1;
|
let y_offset = N + 1;
|
||||||
let tl = yi * y_offset + xi;
|
let tl = yi * y_offset + xi;
|
||||||
mesh.add_triangle(tl, tl + x_offset, tl + y_offset);
|
mesh.add_triangle(tl, tl + x_offset, tl + y_offset);
|
||||||
mesh.add_triangle(tl + x_offset, tl + y_offset, tl + y_offset + x_offset);
|
mesh.add_triangle(tl + x_offset, tl + y_offset, tl + y_offset + x_offset);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ui.painter().add(Shape::mesh(mesh)); // fill
|
||||||
|
|
||||||
|
ui.painter().rect_stroke(rect, 0.0, visuals.bg_stroke); // outline
|
||||||
|
|
||||||
|
// Show where the slider is at:
|
||||||
|
let x = lerp(rect.left()..=rect.right(), *x_value);
|
||||||
|
let y = lerp(rect.bottom()..=rect.top(), *y_value);
|
||||||
|
let picked_color = color_at(*x_value, *y_value);
|
||||||
|
ui.painter().add(epaint::CircleShape {
|
||||||
|
center: pos2(x, y),
|
||||||
|
radius: rect.width() / 12.0,
|
||||||
|
fill: picked_color,
|
||||||
|
stroke: Stroke::new(visuals.fg_stroke.width, contrast_color(picked_color)),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
ui.painter().add(Shape::mesh(mesh)); // fill
|
|
||||||
|
|
||||||
ui.painter().rect_stroke(rect, 0.0, visuals.bg_stroke); // outline
|
|
||||||
|
|
||||||
// Show where the slider is at:
|
|
||||||
let x = lerp(rect.left()..=rect.right(), *x_value);
|
|
||||||
let y = lerp(rect.bottom()..=rect.top(), *y_value);
|
|
||||||
let picked_color = color_at(*x_value, *y_value);
|
|
||||||
ui.painter().add(epaint::CircleShape {
|
|
||||||
center: pos2(x, y),
|
|
||||||
radius: rect.width() / 12.0,
|
|
||||||
fill: picked_color,
|
|
||||||
stroke: Stroke::new(visuals.fg_stroke.width, contrast_color(picked_color)),
|
|
||||||
});
|
|
||||||
|
|
||||||
response
|
response
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,22 +78,24 @@ impl Widget for Hyperlink {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let color = ui.visuals().hyperlink_color;
|
if ui.is_rect_visible(response.rect) {
|
||||||
let visuals = ui.style().interact(&response);
|
let color = ui.visuals().hyperlink_color;
|
||||||
|
let visuals = ui.style().interact(&response);
|
||||||
|
|
||||||
let underline = if response.hovered() || response.has_focus() {
|
let underline = if response.hovered() || response.has_focus() {
|
||||||
Stroke::new(visuals.fg_stroke.width, color)
|
Stroke::new(visuals.fg_stroke.width, color)
|
||||||
} else {
|
} else {
|
||||||
Stroke::none()
|
Stroke::none()
|
||||||
};
|
};
|
||||||
|
|
||||||
ui.painter().add(epaint::TextShape {
|
ui.painter().add(epaint::TextShape {
|
||||||
pos,
|
pos,
|
||||||
galley: text_galley.galley,
|
galley: text_galley.galley,
|
||||||
override_text_color: Some(color),
|
override_text_color: Some(color),
|
||||||
underline,
|
underline,
|
||||||
angle: 0.0,
|
angle: 0.0,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
response.on_hover_text(url)
|
response.on_hover_text(url)
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,27 +68,29 @@ impl Image {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn paint_at(&self, ui: &mut Ui, rect: Rect) {
|
pub fn paint_at(&self, ui: &mut Ui, rect: Rect) {
|
||||||
use epaint::*;
|
if ui.is_rect_visible(rect) {
|
||||||
let Self {
|
use epaint::*;
|
||||||
texture_id,
|
let Self {
|
||||||
uv,
|
texture_id,
|
||||||
size: _,
|
uv,
|
||||||
bg_fill,
|
size: _,
|
||||||
tint,
|
bg_fill,
|
||||||
sense: _,
|
tint,
|
||||||
} = self;
|
sense: _,
|
||||||
|
} = self;
|
||||||
|
|
||||||
if *bg_fill != Default::default() {
|
if *bg_fill != Default::default() {
|
||||||
let mut mesh = Mesh::default();
|
let mut mesh = Mesh::default();
|
||||||
mesh.add_colored_rect(rect, *bg_fill);
|
mesh.add_colored_rect(rect, *bg_fill);
|
||||||
ui.painter().add(Shape::mesh(mesh));
|
ui.painter().add(Shape::mesh(mesh));
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
// TODO: builder pattern for Mesh
|
// TODO: builder pattern for Mesh
|
||||||
let mut mesh = Mesh::with_texture(*texture_id);
|
let mut mesh = Mesh::with_texture(*texture_id);
|
||||||
mesh.add_rect_with_uv(rect, *uv, *tint);
|
mesh.add_rect_with_uv(rect, *uv, *tint);
|
||||||
ui.painter().add(Shape::mesh(mesh));
|
ui.painter().add(Shape::mesh(mesh));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -237,27 +237,30 @@ impl Widget for Label {
|
||||||
fn ui(self, ui: &mut Ui) -> Response {
|
fn ui(self, ui: &mut Ui) -> Response {
|
||||||
let (pos, text_galley, response) = self.layout_in_ui(ui);
|
let (pos, text_galley, response) = self.layout_in_ui(ui);
|
||||||
response.widget_info(|| WidgetInfo::labeled(WidgetType::Label, text_galley.text()));
|
response.widget_info(|| WidgetInfo::labeled(WidgetType::Label, text_galley.text()));
|
||||||
let response_color = ui.style().interact(&response).text_color();
|
|
||||||
|
|
||||||
let underline = if response.has_focus() {
|
if ui.is_rect_visible(response.rect) {
|
||||||
Stroke::new(1.0, response_color)
|
let response_color = ui.style().interact(&response).text_color();
|
||||||
} else {
|
|
||||||
Stroke::none()
|
|
||||||
};
|
|
||||||
|
|
||||||
let override_text_color = if text_galley.galley_has_color {
|
let underline = if response.has_focus() {
|
||||||
None
|
Stroke::new(1.0, response_color)
|
||||||
} else {
|
} else {
|
||||||
Some(response_color)
|
Stroke::none()
|
||||||
};
|
};
|
||||||
|
|
||||||
ui.painter().add(epaint::TextShape {
|
let override_text_color = if text_galley.galley_has_color {
|
||||||
pos,
|
None
|
||||||
galley: text_galley.galley,
|
} else {
|
||||||
override_text_color,
|
Some(response_color)
|
||||||
underline,
|
};
|
||||||
angle: 0.0,
|
|
||||||
});
|
ui.painter().add(epaint::TextShape {
|
||||||
|
pos,
|
||||||
|
galley: text_galley.galley,
|
||||||
|
override_text_color,
|
||||||
|
underline,
|
||||||
|
angle: 0.0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
response
|
response
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,72 +71,77 @@ impl Widget for ProgressBar {
|
||||||
let height = ui.spacing().interact_size.y;
|
let height = ui.spacing().interact_size.y;
|
||||||
let (outer_rect, response) =
|
let (outer_rect, response) =
|
||||||
ui.allocate_exact_size(vec2(desired_width, height), Sense::hover());
|
ui.allocate_exact_size(vec2(desired_width, height), Sense::hover());
|
||||||
let visuals = ui.style().visuals.clone();
|
|
||||||
let corner_radius = outer_rect.height() / 2.0;
|
|
||||||
ui.painter().rect(
|
|
||||||
outer_rect,
|
|
||||||
corner_radius,
|
|
||||||
visuals.extreme_bg_color,
|
|
||||||
Stroke::none(),
|
|
||||||
);
|
|
||||||
let inner_rect = Rect::from_min_size(
|
|
||||||
outer_rect.min,
|
|
||||||
vec2(
|
|
||||||
(outer_rect.width() * progress).at_least(outer_rect.height()),
|
|
||||||
outer_rect.height(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
let (dark, bright) = (0.7, 1.0);
|
if ui.is_rect_visible(response.rect) {
|
||||||
let color_factor = if animate {
|
let visuals = ui.style().visuals.clone();
|
||||||
lerp(dark..=bright, ui.input().time.cos().abs())
|
let corner_radius = outer_rect.height() / 2.0;
|
||||||
} else {
|
ui.painter().rect(
|
||||||
bright
|
outer_rect,
|
||||||
};
|
corner_radius,
|
||||||
|
visuals.extreme_bg_color,
|
||||||
ui.painter().rect(
|
Stroke::none(),
|
||||||
inner_rect,
|
|
||||||
corner_radius,
|
|
||||||
Color32::from(Rgba::from(visuals.selection.bg_fill) * color_factor as f32),
|
|
||||||
Stroke::none(),
|
|
||||||
);
|
|
||||||
|
|
||||||
if animate {
|
|
||||||
let n_points = 20;
|
|
||||||
let start_angle = ui.input().time as f64 * 360f64.to_radians();
|
|
||||||
let end_angle = start_angle + 240f64.to_radians() * ui.input().time.sin();
|
|
||||||
let circle_radius = corner_radius - 2.0;
|
|
||||||
let points: Vec<Pos2> = (0..n_points)
|
|
||||||
.map(|i| {
|
|
||||||
let angle = lerp(start_angle..=end_angle, i as f64 / n_points as f64);
|
|
||||||
let (sin, cos) = angle.sin_cos();
|
|
||||||
inner_rect.right_center()
|
|
||||||
+ circle_radius * vec2(cos as f32, sin as f32)
|
|
||||||
+ vec2(-corner_radius, 0.0)
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
ui.painter().add(Shape::line(
|
|
||||||
points,
|
|
||||||
Stroke::new(2.0, visuals.faint_bg_color),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(text_kind) = text {
|
|
||||||
let text = match text_kind {
|
|
||||||
ProgressBarText::Custom(text) => text,
|
|
||||||
ProgressBarText::Percentage => format!("{}%", (progress * 100.0) as usize).into(),
|
|
||||||
};
|
|
||||||
let galley = text.into_galley(ui, Some(false), f32::INFINITY, TextStyle::Button);
|
|
||||||
let text_pos = outer_rect.left_center() - Vec2::new(0.0, galley.size().y / 2.0)
|
|
||||||
+ vec2(ui.spacing().item_spacing.x, 0.0);
|
|
||||||
let text_color = visuals
|
|
||||||
.override_text_color
|
|
||||||
.unwrap_or(visuals.selection.stroke.color);
|
|
||||||
galley.paint_with_fallback_color(
|
|
||||||
&ui.painter().sub_region(outer_rect),
|
|
||||||
text_pos,
|
|
||||||
text_color,
|
|
||||||
);
|
);
|
||||||
|
let inner_rect = Rect::from_min_size(
|
||||||
|
outer_rect.min,
|
||||||
|
vec2(
|
||||||
|
(outer_rect.width() * progress).at_least(outer_rect.height()),
|
||||||
|
outer_rect.height(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
let (dark, bright) = (0.7, 1.0);
|
||||||
|
let color_factor = if animate {
|
||||||
|
lerp(dark..=bright, ui.input().time.cos().abs())
|
||||||
|
} else {
|
||||||
|
bright
|
||||||
|
};
|
||||||
|
|
||||||
|
ui.painter().rect(
|
||||||
|
inner_rect,
|
||||||
|
corner_radius,
|
||||||
|
Color32::from(Rgba::from(visuals.selection.bg_fill) * color_factor as f32),
|
||||||
|
Stroke::none(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if animate {
|
||||||
|
let n_points = 20;
|
||||||
|
let start_angle = ui.input().time as f64 * 360f64.to_radians();
|
||||||
|
let end_angle = start_angle + 240f64.to_radians() * ui.input().time.sin();
|
||||||
|
let circle_radius = corner_radius - 2.0;
|
||||||
|
let points: Vec<Pos2> = (0..n_points)
|
||||||
|
.map(|i| {
|
||||||
|
let angle = lerp(start_angle..=end_angle, i as f64 / n_points as f64);
|
||||||
|
let (sin, cos) = angle.sin_cos();
|
||||||
|
inner_rect.right_center()
|
||||||
|
+ circle_radius * vec2(cos as f32, sin as f32)
|
||||||
|
+ vec2(-corner_radius, 0.0)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
ui.painter().add(Shape::line(
|
||||||
|
points,
|
||||||
|
Stroke::new(2.0, visuals.faint_bg_color),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(text_kind) = text {
|
||||||
|
let text = match text_kind {
|
||||||
|
ProgressBarText::Custom(text) => text,
|
||||||
|
ProgressBarText::Percentage => {
|
||||||
|
format!("{}%", (progress * 100.0) as usize).into()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let galley = text.into_galley(ui, Some(false), f32::INFINITY, TextStyle::Button);
|
||||||
|
let text_pos = outer_rect.left_center() - Vec2::new(0.0, galley.size().y / 2.0)
|
||||||
|
+ vec2(ui.spacing().item_spacing.x, 0.0);
|
||||||
|
let text_color = visuals
|
||||||
|
.override_text_color
|
||||||
|
.unwrap_or(visuals.selection.stroke.color);
|
||||||
|
galley.paint_with_fallback_color(
|
||||||
|
&ui.painter().sub_region(outer_rect),
|
||||||
|
text_pos,
|
||||||
|
text_color,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
response
|
response
|
||||||
|
|
|
@ -58,22 +58,25 @@ impl Widget for SelectableLabel {
|
||||||
WidgetInfo::selected(WidgetType::SelectableLabel, selected, text.text())
|
WidgetInfo::selected(WidgetType::SelectableLabel, selected, text.text())
|
||||||
});
|
});
|
||||||
|
|
||||||
let text_pos = ui
|
if ui.is_rect_visible(response.rect) {
|
||||||
.layout()
|
let text_pos = ui
|
||||||
.align_size_within_rect(text.size(), rect.shrink2(button_padding))
|
.layout()
|
||||||
.min;
|
.align_size_within_rect(text.size(), rect.shrink2(button_padding))
|
||||||
|
.min;
|
||||||
|
|
||||||
let visuals = ui.style().interact_selectable(&response, selected);
|
let visuals = ui.style().interact_selectable(&response, selected);
|
||||||
|
|
||||||
if selected || response.hovered() || response.has_focus() {
|
if selected || response.hovered() || response.has_focus() {
|
||||||
let rect = rect.expand(visuals.expansion);
|
let rect = rect.expand(visuals.expansion);
|
||||||
|
|
||||||
let corner_radius = 2.0;
|
let corner_radius = 2.0;
|
||||||
ui.painter()
|
ui.painter()
|
||||||
.rect(rect, corner_radius, visuals.bg_fill, visuals.bg_stroke);
|
.rect(rect, corner_radius, visuals.bg_fill, visuals.bg_stroke);
|
||||||
|
}
|
||||||
|
|
||||||
|
text.paint_with_visuals(ui.painter(), text_pos, &visuals);
|
||||||
}
|
}
|
||||||
|
|
||||||
text.paint_with_visuals(ui.painter(), text_pos, &visuals);
|
|
||||||
response
|
response
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,19 +68,23 @@ impl Widget for Separator {
|
||||||
};
|
};
|
||||||
|
|
||||||
let (rect, response) = ui.allocate_at_least(size, Sense::hover());
|
let (rect, response) = ui.allocate_at_least(size, Sense::hover());
|
||||||
let points = if is_horizontal_line {
|
|
||||||
[
|
if ui.is_rect_visible(response.rect) {
|
||||||
pos2(rect.left(), rect.center().y),
|
let points = if is_horizontal_line {
|
||||||
pos2(rect.right(), rect.center().y),
|
[
|
||||||
]
|
pos2(rect.left(), rect.center().y),
|
||||||
} else {
|
pos2(rect.right(), rect.center().y),
|
||||||
[
|
]
|
||||||
pos2(rect.center().x, rect.top()),
|
} else {
|
||||||
pos2(rect.center().x, rect.bottom()),
|
[
|
||||||
]
|
pos2(rect.center().x, rect.top()),
|
||||||
};
|
pos2(rect.center().x, rect.bottom()),
|
||||||
let stroke = ui.visuals().widgets.noninteractive.bg_stroke;
|
]
|
||||||
ui.painter().line_segment(points, stroke);
|
};
|
||||||
|
let stroke = ui.visuals().widgets.noninteractive.bg_stroke;
|
||||||
|
ui.painter().line_segment(points, stroke);
|
||||||
|
}
|
||||||
|
|
||||||
response
|
response
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -320,7 +320,7 @@ impl<'a> Slider<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Paint it:
|
// Paint it:
|
||||||
{
|
if ui.is_rect_visible(response.rect) {
|
||||||
let value = self.get_value();
|
let value = self.get_value();
|
||||||
|
|
||||||
let rail_radius = ui
|
let rail_radius = ui
|
||||||
|
|
|
@ -507,41 +507,43 @@ impl<'t> TextEdit<'t> {
|
||||||
text_draw_pos -= vec2(offset_x, 0.0);
|
text_draw_pos -= vec2(offset_x, 0.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
painter.galley(text_draw_pos, galley.clone());
|
if ui.is_rect_visible(rect) {
|
||||||
|
painter.galley(text_draw_pos, galley.clone());
|
||||||
|
|
||||||
if text.as_ref().is_empty() && !hint_text.is_empty() {
|
if text.as_ref().is_empty() && !hint_text.is_empty() {
|
||||||
let hint_text_color = ui.visuals().weak_text_color();
|
let hint_text_color = ui.visuals().weak_text_color();
|
||||||
let galley = if multiline {
|
let galley = if multiline {
|
||||||
hint_text.into_galley(ui, Some(true), desired_size.x, text_style)
|
hint_text.into_galley(ui, Some(true), desired_size.x, text_style)
|
||||||
} else {
|
} else {
|
||||||
hint_text.into_galley(ui, Some(false), f32::INFINITY, text_style)
|
hint_text.into_galley(ui, Some(false), f32::INFINITY, text_style)
|
||||||
};
|
};
|
||||||
galley.paint_with_fallback_color(&painter, response.rect.min, hint_text_color);
|
galley.paint_with_fallback_color(&painter, response.rect.min, hint_text_color);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ui.memory().has_focus(id) {
|
if ui.memory().has_focus(id) {
|
||||||
if let Some(cursor_range) = state.cursor_range(&*galley) {
|
if let Some(cursor_range) = state.cursor_range(&*galley) {
|
||||||
// We paint the cursor on top of the text, in case
|
// We paint the cursor on top of the text, in case
|
||||||
// the text galley has backgrounds (as e.g. `code` snippets in markup do).
|
// the text galley has backgrounds (as e.g. `code` snippets in markup do).
|
||||||
paint_cursor_selection(ui, &painter, text_draw_pos, &galley, &cursor_range);
|
paint_cursor_selection(ui, &painter, text_draw_pos, &galley, &cursor_range);
|
||||||
paint_cursor_end(
|
paint_cursor_end(
|
||||||
ui,
|
ui,
|
||||||
row_height,
|
row_height,
|
||||||
&painter,
|
&painter,
|
||||||
text_draw_pos,
|
text_draw_pos,
|
||||||
&galley,
|
&galley,
|
||||||
&cursor_range.primary,
|
&cursor_range.primary,
|
||||||
);
|
|
||||||
|
|
||||||
if interactive && text.is_mutable() {
|
|
||||||
// egui_web uses `text_cursor_pos` when showing IME,
|
|
||||||
// so only set it when text is editable!
|
|
||||||
ui.ctx().output().text_cursor_pos = Some(
|
|
||||||
galley
|
|
||||||
.pos_from_cursor(&cursor_range.primary)
|
|
||||||
.translate(response.rect.min.to_vec2())
|
|
||||||
.left_top(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if interactive && text.is_mutable() {
|
||||||
|
// egui_web uses `text_cursor_pos` when showing IME,
|
||||||
|
// so only set it when text is editable and visible!
|
||||||
|
ui.ctx().output().text_cursor_pos = Some(
|
||||||
|
galley
|
||||||
|
.pos_from_cursor(&cursor_range.primary)
|
||||||
|
.translate(response.rect.min.to_vec2())
|
||||||
|
.left_top(),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,24 +41,27 @@ pub fn toggle_ui(ui: &mut egui::Ui, on: &mut bool) -> egui::Response {
|
||||||
response.widget_info(|| egui::WidgetInfo::selected(egui::WidgetType::Checkbox, *on, ""));
|
response.widget_info(|| egui::WidgetInfo::selected(egui::WidgetType::Checkbox, *on, ""));
|
||||||
|
|
||||||
// 4. Paint!
|
// 4. Paint!
|
||||||
// First let's ask for a simple animation from egui.
|
// Make sure we need to paint:
|
||||||
// egui keeps track of changes in the boolean associated with the id and
|
if ui.is_rect_visible(rect) {
|
||||||
// returns an animated value in the 0-1 range for how much "on" we are.
|
// Let's ask for a simple animation from egui.
|
||||||
let how_on = ui.ctx().animate_bool(response.id, *on);
|
// egui keeps track of changes in the boolean associated with the id and
|
||||||
// We will follow the current style by asking
|
// returns an animated value in the 0-1 range for how much "on" we are.
|
||||||
// "how should something that is being interacted with be painted?".
|
let how_on = ui.ctx().animate_bool(response.id, *on);
|
||||||
// This will, for instance, give us different colors when the widget is hovered or clicked.
|
// We will follow the current style by asking
|
||||||
let visuals = ui.style().interact_selectable(&response, *on);
|
// "how should something that is being interacted with be painted?".
|
||||||
// All coordinates are in absolute screen coordinates so we use `rect` to place the elements.
|
// This will, for instance, give us different colors when the widget is hovered or clicked.
|
||||||
let rect = rect.expand(visuals.expansion);
|
let visuals = ui.style().interact_selectable(&response, *on);
|
||||||
let radius = 0.5 * rect.height();
|
// All coordinates are in absolute screen coordinates so we use `rect` to place the elements.
|
||||||
ui.painter()
|
let rect = rect.expand(visuals.expansion);
|
||||||
.rect(rect, radius, visuals.bg_fill, visuals.bg_stroke);
|
let radius = 0.5 * rect.height();
|
||||||
// Paint the circle, animating it from left to right with `how_on`:
|
ui.painter()
|
||||||
let circle_x = egui::lerp((rect.left() + radius)..=(rect.right() - radius), how_on);
|
.rect(rect, radius, visuals.bg_fill, visuals.bg_stroke);
|
||||||
let center = egui::pos2(circle_x, rect.center().y);
|
// Paint the circle, animating it from left to right with `how_on`:
|
||||||
ui.painter()
|
let circle_x = egui::lerp((rect.left() + radius)..=(rect.right() - radius), how_on);
|
||||||
.circle(center, 0.75 * radius, visuals.bg_fill, visuals.fg_stroke);
|
let center = egui::pos2(circle_x, rect.center().y);
|
||||||
|
ui.painter()
|
||||||
|
.circle(center, 0.75 * radius, visuals.bg_fill, visuals.fg_stroke);
|
||||||
|
}
|
||||||
|
|
||||||
// All done! Return the interaction response so the user can check what happened
|
// All done! Return the interaction response so the user can check what happened
|
||||||
// (hovered, clicked, ...) and maybe show a tooltip:
|
// (hovered, clicked, ...) and maybe show a tooltip:
|
||||||
|
@ -76,16 +79,18 @@ fn toggle_ui_compact(ui: &mut egui::Ui, on: &mut bool) -> egui::Response {
|
||||||
}
|
}
|
||||||
response.widget_info(|| egui::WidgetInfo::selected(egui::WidgetType::Checkbox, *on, ""));
|
response.widget_info(|| egui::WidgetInfo::selected(egui::WidgetType::Checkbox, *on, ""));
|
||||||
|
|
||||||
let how_on = ui.ctx().animate_bool(response.id, *on);
|
if ui.is_rect_visible(rect) {
|
||||||
let visuals = ui.style().interact_selectable(&response, *on);
|
let how_on = ui.ctx().animate_bool(response.id, *on);
|
||||||
let rect = rect.expand(visuals.expansion);
|
let visuals = ui.style().interact_selectable(&response, *on);
|
||||||
let radius = 0.5 * rect.height();
|
let rect = rect.expand(visuals.expansion);
|
||||||
ui.painter()
|
let radius = 0.5 * rect.height();
|
||||||
.rect(rect, radius, visuals.bg_fill, visuals.bg_stroke);
|
ui.painter()
|
||||||
let circle_x = egui::lerp((rect.left() + radius)..=(rect.right() - radius), how_on);
|
.rect(rect, radius, visuals.bg_fill, visuals.bg_stroke);
|
||||||
let center = egui::pos2(circle_x, rect.center().y);
|
let circle_x = egui::lerp((rect.left() + radius)..=(rect.right() - radius), how_on);
|
||||||
ui.painter()
|
let center = egui::pos2(circle_x, rect.center().y);
|
||||||
.circle(center, 0.75 * radius, visuals.bg_fill, visuals.fg_stroke);
|
ui.painter()
|
||||||
|
.circle(center, 0.75 * radius, visuals.bg_fill, visuals.fg_stroke);
|
||||||
|
}
|
||||||
|
|
||||||
response
|
response
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue