Add ui.set_enabled(false) to disable all widgets in a Ui
Closes https://github.com/emilk/egui/issues/50
This commit is contained in:
parent
d07a17ac6a
commit
bca722ddf8
20 changed files with 303 additions and 77 deletions
|
@ -13,7 +13,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
|
||||
* Add support for secondary and middle mouse buttons.
|
||||
* Add `Label` methods for code, strong, strikethrough, underline and italics.
|
||||
* Add `ui.group(|ui| { … })` to visually group some widgets within a frame
|
||||
* Add `ui.group(|ui| { … })` to visually group some widgets within a frame.
|
||||
* Add `ui.set_enabled(false)` to disable all widgets in a `Ui` (grayed out and non-interactive).
|
||||
* Add `TextEdit::hint_text` for showing a weak hint text when empty.
|
||||
* `egui::popup::popup_below_widget`: show a popup area below another widget.
|
||||
* Add `Slider::clamp_to_range(bool)`: if set, clamp the incoming and outgoing values to the slider range.
|
||||
|
|
|
@ -45,6 +45,7 @@ pub struct Area {
|
|||
pub(crate) id: Id,
|
||||
movable: bool,
|
||||
interactable: bool,
|
||||
enabled: bool,
|
||||
order: Order,
|
||||
default_pos: Option<Pos2>,
|
||||
new_pos: Option<Pos2>,
|
||||
|
@ -56,6 +57,7 @@ impl Area {
|
|||
id: Id::new(id_source),
|
||||
movable: true,
|
||||
interactable: true,
|
||||
enabled: true,
|
||||
order: Order::Middle,
|
||||
default_pos: None,
|
||||
new_pos: None,
|
||||
|
@ -71,6 +73,15 @@ impl Area {
|
|||
LayerId::new(self.order, self.id)
|
||||
}
|
||||
|
||||
/// If false, no content responds to click
|
||||
/// and widgets will be shown grayed out.
|
||||
/// You won't be able to move the window.
|
||||
/// Default: `true`.
|
||||
pub fn enabled(mut self, enabled: bool) -> Self {
|
||||
self.enabled = enabled;
|
||||
self
|
||||
}
|
||||
|
||||
/// moveable by dragging the area?
|
||||
pub fn movable(mut self, movable: bool) -> Self {
|
||||
self.movable = movable;
|
||||
|
@ -78,8 +89,12 @@ impl Area {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn is_enabled(&self) -> bool {
|
||||
self.enabled
|
||||
}
|
||||
|
||||
pub fn is_movable(&self) -> bool {
|
||||
self.movable
|
||||
self.movable && self.enabled
|
||||
}
|
||||
|
||||
/// If false, clicks goes straight through to what is behind us.
|
||||
|
@ -121,6 +136,7 @@ pub(crate) struct Prepared {
|
|||
layer_id: LayerId,
|
||||
state: State,
|
||||
movable: bool,
|
||||
enabled: bool,
|
||||
}
|
||||
|
||||
impl Area {
|
||||
|
@ -130,6 +146,7 @@ impl Area {
|
|||
movable,
|
||||
order,
|
||||
interactable,
|
||||
enabled,
|
||||
default_pos,
|
||||
new_pos,
|
||||
} = self;
|
||||
|
@ -149,6 +166,7 @@ impl Area {
|
|||
layer_id,
|
||||
state,
|
||||
movable,
|
||||
enabled,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -214,13 +232,15 @@ impl Prepared {
|
|||
clip_rect = clip_rect.intersect(central_area);
|
||||
}
|
||||
|
||||
Ui::new(
|
||||
let mut ui = Ui::new(
|
||||
ctx.clone(),
|
||||
self.layer_id,
|
||||
self.layer_id.id,
|
||||
max_rect,
|
||||
clip_rect,
|
||||
)
|
||||
);
|
||||
ui.set_enabled(self.enabled);
|
||||
ui
|
||||
}
|
||||
|
||||
#[allow(clippy::needless_pass_by_value)] // intentional to swallow up `content_ui`.
|
||||
|
@ -229,6 +249,7 @@ impl Prepared {
|
|||
layer_id,
|
||||
mut state,
|
||||
movable,
|
||||
enabled,
|
||||
} = self;
|
||||
|
||||
state.size = content_ui.min_rect().size();
|
||||
|
@ -247,6 +268,7 @@ impl Prepared {
|
|||
interact_id,
|
||||
state.rect(),
|
||||
sense,
|
||||
enabled,
|
||||
);
|
||||
|
||||
if move_response.dragged() && movable {
|
||||
|
|
|
@ -23,7 +23,7 @@ impl Frame {
|
|||
Self {
|
||||
margin: Vec2::new(8.0, 6.0),
|
||||
corner_radius: 4.0,
|
||||
stroke: style.visuals.widgets.noninteractive.bg_stroke,
|
||||
stroke: style.visuals.window_stroke(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
@ -32,8 +32,8 @@ impl Frame {
|
|||
Self {
|
||||
margin: Vec2::new(8.0, 2.0),
|
||||
corner_radius: 0.0,
|
||||
fill: style.visuals.widgets.noninteractive.bg_fill,
|
||||
stroke: style.visuals.widgets.noninteractive.bg_stroke,
|
||||
fill: style.visuals.window_fill(),
|
||||
stroke: style.visuals.window_stroke(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
@ -42,7 +42,7 @@ impl Frame {
|
|||
Self {
|
||||
margin: Vec2::new(8.0, 8.0),
|
||||
corner_radius: 0.0,
|
||||
fill: style.visuals.widgets.noninteractive.bg_fill,
|
||||
fill: style.visuals.window_fill(),
|
||||
stroke: Default::default(),
|
||||
..Default::default()
|
||||
}
|
||||
|
@ -53,8 +53,8 @@ impl Frame {
|
|||
margin: style.spacing.window_padding,
|
||||
corner_radius: style.visuals.window_corner_radius,
|
||||
shadow: style.visuals.window_shadow,
|
||||
fill: style.visuals.widgets.noninteractive.bg_fill,
|
||||
stroke: style.visuals.widgets.noninteractive.bg_stroke,
|
||||
fill: style.visuals.window_fill(),
|
||||
stroke: style.visuals.window_stroke(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -63,8 +63,8 @@ impl Frame {
|
|||
margin: Vec2::splat(1.0),
|
||||
corner_radius: 2.0,
|
||||
shadow: Shadow::small(),
|
||||
fill: style.visuals.widgets.noninteractive.bg_fill,
|
||||
stroke: style.visuals.widgets.noninteractive.bg_stroke,
|
||||
fill: style.visuals.window_fill(),
|
||||
stroke: style.visuals.window_stroke(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -73,8 +73,8 @@ impl Frame {
|
|||
margin: style.spacing.window_padding,
|
||||
corner_radius: 5.0,
|
||||
shadow: Shadow::small(),
|
||||
fill: style.visuals.widgets.noninteractive.bg_fill,
|
||||
stroke: style.visuals.widgets.noninteractive.bg_stroke,
|
||||
fill: style.visuals.window_fill(),
|
||||
stroke: style.visuals.window_stroke(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -84,7 +84,7 @@ impl Frame {
|
|||
margin: Vec2::new(10.0, 10.0),
|
||||
corner_radius: 5.0,
|
||||
fill: Color32::from_black_alpha(250),
|
||||
stroke: style.visuals.widgets.noninteractive.bg_stroke,
|
||||
stroke: style.visuals.window_stroke(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -70,6 +70,12 @@ impl<'open> Window<'open> {
|
|||
self
|
||||
}
|
||||
|
||||
/// If `false` the window will be grayed out and non-interactive.
|
||||
pub fn enabled(mut self, enabled: bool) -> Self {
|
||||
self.area = self.area.enabled(enabled);
|
||||
self
|
||||
}
|
||||
|
||||
/// Usage: `Window::new(...).mutate(|w| w.resize = w.resize.auto_expand_width(true))`
|
||||
/// Not sure this is a good interface for this.
|
||||
pub fn mutate(mut self, mutate: impl Fn(&mut Self)) -> Self {
|
||||
|
@ -239,8 +245,8 @@ impl<'open> Window<'open> {
|
|||
let is_maximized = !with_title_bar
|
||||
|| collapsing_header::State::is_open(ctx, collapsing_id).unwrap_or_default();
|
||||
let possible = PossibleInteractions {
|
||||
movable: area.is_movable(),
|
||||
resizable: resize.is_resizable() && is_maximized,
|
||||
movable: area.is_enabled() && area.is_movable(),
|
||||
resizable: area.is_enabled() && resize.is_resizable() && is_maximized,
|
||||
};
|
||||
|
||||
let area = area.movable(false); // We move it manually
|
||||
|
|
|
@ -218,6 +218,7 @@ impl CtxRef {
|
|||
// ---------------------------------------------------------------------
|
||||
|
||||
/// Use `ui.interact` instead
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn interact(
|
||||
&self,
|
||||
clip_rect: Rect,
|
||||
|
@ -226,6 +227,7 @@ impl CtxRef {
|
|||
id: Id,
|
||||
rect: Rect,
|
||||
sense: Sense,
|
||||
enabled: bool,
|
||||
) -> Response {
|
||||
let gap = 0.5; // Just to make sure we don't accidentally hover two things at once (a small eps should be sufficient).
|
||||
let interact_rect = rect.expand2(
|
||||
|
@ -234,7 +236,7 @@ impl CtxRef {
|
|||
.at_most(Vec2::splat(5.0)),
|
||||
); // make it easier to click
|
||||
let hovered = self.rect_contains_pointer(layer_id, clip_rect.intersect(interact_rect));
|
||||
self.interact_with_hovered(layer_id, id, rect, sense, hovered)
|
||||
self.interact_with_hovered(layer_id, id, rect, sense, enabled, hovered)
|
||||
}
|
||||
|
||||
/// You specify if a thing is hovered, and the function gives a `Response`.
|
||||
|
@ -244,8 +246,11 @@ impl CtxRef {
|
|||
id: Id,
|
||||
rect: Rect,
|
||||
sense: Sense,
|
||||
enabled: bool,
|
||||
hovered: bool,
|
||||
) -> Response {
|
||||
let hovered = hovered && enabled; // can't even hover disabled widgets
|
||||
|
||||
let has_kb_focus = self.memory().has_kb_focus(id);
|
||||
let lost_kb_focus = self.memory().lost_kb_focus(id);
|
||||
|
||||
|
@ -255,6 +260,7 @@ impl CtxRef {
|
|||
id,
|
||||
rect,
|
||||
sense,
|
||||
enabled,
|
||||
hovered,
|
||||
clicked: Default::default(),
|
||||
double_clicked: Default::default(),
|
||||
|
@ -266,7 +272,7 @@ impl CtxRef {
|
|||
lost_kb_focus,
|
||||
};
|
||||
|
||||
if sense == Sense::hover() || !layer_id.allow_interaction() {
|
||||
if !enabled || sense == Sense::hover() || !layer_id.allow_interaction() {
|
||||
// Not interested or allowed input:
|
||||
return response;
|
||||
}
|
||||
|
|
|
@ -22,6 +22,10 @@ pub struct Painter {
|
|||
/// Everything painted in this `Painter` will be clipped against this.
|
||||
/// This means nothing outside of this rectangle will be visible on screen.
|
||||
clip_rect: Rect,
|
||||
|
||||
/// If set, all shapes will have their colors modified to be closer to this.
|
||||
/// This is used to implement grayed out interfaces.
|
||||
fade_to_color: Option<Color32>,
|
||||
}
|
||||
|
||||
impl Painter {
|
||||
|
@ -30,6 +34,7 @@ impl Painter {
|
|||
ctx,
|
||||
layer_id,
|
||||
clip_rect,
|
||||
fade_to_color: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -39,6 +44,7 @@ impl Painter {
|
|||
ctx: self.ctx,
|
||||
layer_id,
|
||||
clip_rect: self.clip_rect,
|
||||
fade_to_color: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -47,6 +53,11 @@ impl Painter {
|
|||
self.layer_id = layer_id;
|
||||
}
|
||||
|
||||
/// If set, colors will be modified to look like this
|
||||
pub(crate) fn set_fade_to_color(&mut self, fade_to_color: Option<Color32>) {
|
||||
self.fade_to_color = fade_to_color;
|
||||
}
|
||||
|
||||
/// Create a painter for a sub-region of this `Painter`.
|
||||
///
|
||||
/// The clip-rect of the returned `Painter` will be the intersection
|
||||
|
@ -106,10 +117,17 @@ impl Painter {
|
|||
|
||||
/// ## Low level
|
||||
impl Painter {
|
||||
fn transform_shape(&self, shape: &mut Shape) {
|
||||
if let Some(fade_to_color) = self.fade_to_color {
|
||||
tint_shape_towards(shape, fade_to_color);
|
||||
}
|
||||
}
|
||||
|
||||
/// It is up to the caller to make sure there is room for this.
|
||||
/// Can be used for free painting.
|
||||
/// NOTE: all coordinates are screen coordinates!
|
||||
pub fn add(&self, shape: Shape) -> ShapeIdx {
|
||||
pub fn add(&self, mut shape: Shape) -> ShapeIdx {
|
||||
self.transform_shape(&mut shape);
|
||||
self.ctx
|
||||
.graphics()
|
||||
.list(self.layer_id)
|
||||
|
@ -119,8 +137,14 @@ impl Painter {
|
|||
/// Add many shapes at once.
|
||||
///
|
||||
/// Calling this once is generally faster than calling [`Self::add`] multiple times.
|
||||
pub fn extend(&self, shapes: Vec<Shape>) {
|
||||
pub fn extend(&self, mut shapes: Vec<Shape>) {
|
||||
if !shapes.is_empty() {
|
||||
if self.fade_to_color.is_some() {
|
||||
for shape in &mut shapes {
|
||||
self.transform_shape(shape);
|
||||
}
|
||||
}
|
||||
|
||||
self.ctx
|
||||
.graphics()
|
||||
.list(self.layer_id)
|
||||
|
@ -129,7 +153,8 @@ impl Painter {
|
|||
}
|
||||
|
||||
/// Modify an existing [`Shape`].
|
||||
pub fn set(&self, idx: ShapeIdx, shape: Shape) {
|
||||
pub fn set(&self, idx: ShapeIdx, mut shape: Shape) {
|
||||
self.transform_shape(&mut shape);
|
||||
self.ctx
|
||||
.graphics()
|
||||
.list(self.layer_id)
|
||||
|
@ -294,3 +319,9 @@ impl Painter {
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn tint_shape_towards(shape: &mut Shape, target: Color32) {
|
||||
epaint::shape_transform::adjust_colors(shape, &|color| {
|
||||
*color = crate::color::tint_color_towards(*color, target);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -29,6 +29,10 @@ pub struct Response {
|
|||
/// The senses (click and/or drag) that the widget was interested in (if any).
|
||||
pub sense: Sense,
|
||||
|
||||
/// Was the widget enabled?
|
||||
/// If `false`, there was no interaction attempted (not even hover).
|
||||
pub(crate) enabled: bool,
|
||||
|
||||
// OUT:
|
||||
/// The pointer is hovering above this widget or the widget was clicked/tapped this frame.
|
||||
pub(crate) hovered: bool,
|
||||
|
@ -68,6 +72,7 @@ impl std::fmt::Debug for Response {
|
|||
id,
|
||||
rect,
|
||||
sense,
|
||||
enabled,
|
||||
hovered,
|
||||
clicked,
|
||||
double_clicked,
|
||||
|
@ -83,6 +88,7 @@ impl std::fmt::Debug for Response {
|
|||
.field("id", id)
|
||||
.field("rect", rect)
|
||||
.field("sense", sense)
|
||||
.field("enabled", enabled)
|
||||
.field("hovered", hovered)
|
||||
.field("clicked", clicked)
|
||||
.field("double_clicked", double_clicked)
|
||||
|
@ -117,6 +123,13 @@ impl Response {
|
|||
self.double_clicked[PointerButton::Primary as usize]
|
||||
}
|
||||
|
||||
/// Was the widget enabled?
|
||||
/// If false, there was no interaction attempted
|
||||
/// and the widget should be drawn in a gray disabled look.
|
||||
pub fn enabled(&self) -> bool {
|
||||
self.enabled
|
||||
}
|
||||
|
||||
/// The pointer is hovering above this widget or the widget was clicked/tapped this frame.
|
||||
pub fn hovered(&self) -> bool {
|
||||
self.hovered
|
||||
|
@ -208,8 +221,14 @@ impl Response {
|
|||
/// if response.clicked() { /* … */ }
|
||||
/// ```
|
||||
pub fn interact(&self, sense: Sense) -> Self {
|
||||
self.ctx
|
||||
.interact_with_hovered(self.layer_id, self.id, self.rect, sense, self.hovered)
|
||||
self.ctx.interact_with_hovered(
|
||||
self.layer_id,
|
||||
self.id,
|
||||
self.rect,
|
||||
sense,
|
||||
self.enabled,
|
||||
self.hovered,
|
||||
)
|
||||
}
|
||||
|
||||
/// Move the scroll to this UI with the specified alignment.
|
||||
|
@ -247,6 +266,7 @@ impl Response {
|
|||
id: self.id,
|
||||
rect: self.rect.union(other.rect),
|
||||
sense: self.sense.union(other.sense),
|
||||
enabled: self.enabled || other.enabled,
|
||||
hovered: self.hovered || other.hovered,
|
||||
clicked: [
|
||||
self.clicked[0] || other.clicked[0],
|
||||
|
|
|
@ -184,12 +184,20 @@ impl Visuals {
|
|||
}
|
||||
|
||||
pub fn weak_text_color(&self) -> Color32 {
|
||||
self.widgets.disabled.text_color()
|
||||
crate::color::tint_color_towards(self.text_color(), self.window_fill())
|
||||
}
|
||||
|
||||
pub fn strong_text_color(&self) -> Color32 {
|
||||
self.widgets.active.text_color()
|
||||
}
|
||||
|
||||
pub fn window_fill(&self) -> Color32 {
|
||||
self.widgets.noninteractive.bg_fill
|
||||
}
|
||||
|
||||
pub fn window_stroke(&self) -> Stroke {
|
||||
self.widgets.noninteractive.bg_stroke
|
||||
}
|
||||
}
|
||||
|
||||
/// Selected text, selected elements etc
|
||||
|
@ -210,8 +218,6 @@ pub struct Widgets {
|
|||
/// * `noninteractive.bg_fill` is the background color of windows.
|
||||
/// * `noninteractive.fg_stroke` is the normal text color.
|
||||
pub noninteractive: WidgetVisuals,
|
||||
/// The style of a disabled button.
|
||||
pub disabled: WidgetVisuals,
|
||||
/// The style of an interactive widget, such as a button, at rest.
|
||||
pub inactive: WidgetVisuals,
|
||||
/// The style of an interactive widget while you hover it.
|
||||
|
@ -224,8 +230,6 @@ impl Widgets {
|
|||
pub fn style(&self, response: &Response) -> &WidgetVisuals {
|
||||
if response.is_pointer_button_down_on() || response.has_kb_focus {
|
||||
&self.active
|
||||
} else if response.sense == crate::Sense::hover() {
|
||||
&self.disabled
|
||||
} else if response.hovered() {
|
||||
&self.hovered
|
||||
} else {
|
||||
|
@ -374,19 +378,12 @@ impl Widgets {
|
|||
pub fn dark() -> Self {
|
||||
Self {
|
||||
noninteractive: WidgetVisuals {
|
||||
bg_fill: Color32::from_gray(30), // window background
|
||||
bg_stroke: Stroke::new(1.0, Color32::from_gray(65)), // window outline
|
||||
bg_fill: Color32::from_gray(30), // window background
|
||||
fg_stroke: Stroke::new(1.0, Color32::from_gray(160)), // normal text color
|
||||
corner_radius: 4.0,
|
||||
expansion: 0.0,
|
||||
},
|
||||
disabled: WidgetVisuals {
|
||||
bg_fill: Color32::from_gray(40), // Should look grayed out
|
||||
bg_stroke: Stroke::new(1.0, Color32::from_gray(70)),
|
||||
fg_stroke: Stroke::new(1.0, Color32::from_gray(110)), // Should look grayed out. Also used for "weak" text color.
|
||||
corner_radius: 4.0,
|
||||
expansion: 0.0,
|
||||
},
|
||||
inactive: WidgetVisuals {
|
||||
bg_fill: Color32::from_gray(70),
|
||||
bg_stroke: Default::default(),
|
||||
|
@ -414,16 +411,9 @@ impl Widgets {
|
|||
pub fn light() -> Self {
|
||||
Self {
|
||||
noninteractive: WidgetVisuals {
|
||||
bg_fill: Color32::from_gray(220), // window background
|
||||
bg_stroke: Stroke::new(1.0, Color32::from_gray(180)), // window outline
|
||||
bg_fill: Color32::from_gray(220), // window background
|
||||
fg_stroke: Stroke::new(1.0, Color32::from_gray(70)), // normal text color
|
||||
corner_radius: 4.0,
|
||||
expansion: 0.0,
|
||||
},
|
||||
disabled: WidgetVisuals {
|
||||
bg_fill: Color32::from_gray(215), // Should look grayed out
|
||||
bg_stroke: Stroke::new(1.0, Color32::from_gray(185)),
|
||||
fg_stroke: Stroke::new(1.0, Color32::from_gray(145)), // Should look grayed out. Also used for "weak" text color.
|
||||
fg_stroke: Stroke::new(1.0, Color32::from_gray(70)), // normal text color
|
||||
corner_radius: 4.0,
|
||||
expansion: 0.0,
|
||||
},
|
||||
|
@ -542,7 +532,6 @@ impl Widgets {
|
|||
active,
|
||||
hovered,
|
||||
inactive,
|
||||
disabled,
|
||||
noninteractive,
|
||||
} = self;
|
||||
|
||||
|
@ -550,10 +539,6 @@ impl Widgets {
|
|||
ui.label("The style of a widget that you cannot interact with.");
|
||||
noninteractive.ui(ui)
|
||||
});
|
||||
ui.collapsing("interactive & disabled", |ui| {
|
||||
ui.label("The style of a disabled button.");
|
||||
disabled.ui(ui)
|
||||
});
|
||||
ui.collapsing("interactive & inactive", |ui| {
|
||||
ui.label("The style of an interactive widget, such as a button, at rest.");
|
||||
inactive.ui(ui)
|
||||
|
|
|
@ -46,6 +46,10 @@ pub struct Ui {
|
|||
|
||||
/// Handles the `Ui` size and the placement of new widgets.
|
||||
placer: Placer,
|
||||
|
||||
/// If false we are unresponsive to input,
|
||||
/// and all widgets will assume a gray style.
|
||||
enabled: bool,
|
||||
}
|
||||
|
||||
impl Ui {
|
||||
|
@ -60,6 +64,7 @@ impl Ui {
|
|||
painter: Painter::new(ctx, layer_id, clip_rect),
|
||||
style,
|
||||
placer: Placer::new(max_rect, Layout::default()),
|
||||
enabled: true,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -72,6 +77,7 @@ impl Ui {
|
|||
painter: self.painter.clone(),
|
||||
style: self.style.clone(),
|
||||
placer: Placer::new(max_rect, layout),
|
||||
enabled: self.enabled,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -166,6 +172,39 @@ impl Ui {
|
|||
&self.painter
|
||||
}
|
||||
|
||||
/// If `false`, the `Ui` does not allow any interaction and
|
||||
/// the widgets in it will draw with a gray look.
|
||||
pub fn enabled(&self) -> bool {
|
||||
self.enabled
|
||||
}
|
||||
|
||||
/// Calling `set_enabled(false)` will cause the `Ui` to deny all future interaction
|
||||
/// and all the widgets will draw with a gray look.
|
||||
///
|
||||
/// Calling `set_enabled(true)` has no effect - it will NOT re-enable the `Ui` once disabled.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```
|
||||
/// # let ui = &mut egui::Ui::__test();
|
||||
/// # let mut enabled = true;
|
||||
/// ui.group(|ui|{
|
||||
/// ui.checkbox(&mut enabled, "Enable subsection");
|
||||
/// ui.set_enabled(enabled);
|
||||
/// if ui.button("Button that is not always clickable").clicked() {
|
||||
/// /* … */
|
||||
/// }
|
||||
/// });
|
||||
/// ```
|
||||
pub fn set_enabled(&mut self, enabled: bool) {
|
||||
self.enabled &= enabled;
|
||||
if self.enabled {
|
||||
self.painter.set_fade_to_color(None);
|
||||
} else {
|
||||
self.painter
|
||||
.set_fade_to_color(Some(self.visuals().window_fill()));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn layout(&self) -> &Layout {
|
||||
self.placer.layout()
|
||||
}
|
||||
|
@ -427,6 +466,7 @@ impl Ui {
|
|||
id,
|
||||
rect,
|
||||
sense,
|
||||
self.enabled,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -68,6 +68,8 @@ impl Button {
|
|||
|
||||
/// If you set this to `false`, the button will be grayed out and un-clickable.
|
||||
/// `enabled(false)` has the same effect as calling `sense(Sense::hover())`.
|
||||
///
|
||||
/// This is a convenience for [`Ui::set_enabled`].
|
||||
pub fn enabled(mut self, enabled: bool) -> Self {
|
||||
if !enabled {
|
||||
self.sense = Sense::hover();
|
||||
|
@ -76,8 +78,8 @@ impl Button {
|
|||
}
|
||||
}
|
||||
|
||||
impl Widget for Button {
|
||||
fn ui(self, ui: &mut Ui) -> Response {
|
||||
impl Button {
|
||||
fn enabled_ui(self, ui: &mut Ui) -> Response {
|
||||
let Button {
|
||||
text,
|
||||
text_color,
|
||||
|
@ -136,6 +138,22 @@ impl Widget for Button {
|
|||
}
|
||||
}
|
||||
|
||||
impl Widget for Button {
|
||||
fn ui(self, ui: &mut Ui) -> Response {
|
||||
let button_enabled = self.sense != Sense::hover();
|
||||
if button_enabled || !ui.enabled() {
|
||||
self.enabled_ui(ui)
|
||||
} else {
|
||||
// We need get a temporary disabled `Ui` to get that grayed out look:
|
||||
ui.wrap(|ui| {
|
||||
ui.set_enabled(false);
|
||||
self.enabled_ui(ui)
|
||||
})
|
||||
.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// TODO: allow checkbox without a text label
|
||||
|
|
|
@ -160,15 +160,15 @@ impl Label {
|
|||
..
|
||||
} = *self;
|
||||
|
||||
let text_color = self.text_color.unwrap_or_else(|| {
|
||||
if strong {
|
||||
ui.visuals().strong_text_color()
|
||||
} else if weak {
|
||||
ui.visuals().weak_text_color()
|
||||
} else {
|
||||
ui.visuals().text_color()
|
||||
}
|
||||
});
|
||||
let text_color = if let Some(text_color) = self.text_color {
|
||||
text_color
|
||||
} else if strong {
|
||||
ui.visuals().strong_text_color()
|
||||
} else if weak {
|
||||
ui.visuals().weak_text_color()
|
||||
} else {
|
||||
ui.visuals().text_color()
|
||||
};
|
||||
|
||||
if code {
|
||||
background_color = ui.visuals().code_bg_color;
|
||||
|
|
|
@ -48,16 +48,19 @@ impl Widget for SelectableLabel {
|
|||
|
||||
if selected || response.hovered() {
|
||||
let rect = rect.expand(visuals.expansion);
|
||||
|
||||
let fill = if selected {
|
||||
ui.visuals().selection.bg_fill
|
||||
} else {
|
||||
Default::default()
|
||||
};
|
||||
|
||||
let stroke = if selected {
|
||||
ui.visuals().selection.stroke
|
||||
} else {
|
||||
visuals.bg_stroke
|
||||
};
|
||||
|
||||
let corner_radius = 2.0;
|
||||
ui.painter().rect(rect, corner_radius, fill, stroke);
|
||||
}
|
||||
|
|
|
@ -51,20 +51,24 @@ pub fn drop_target<R>(
|
|||
|
||||
let style = if is_being_dragged && can_accept_what_is_being_dragged && response.hovered() {
|
||||
ui.visuals().widgets.active
|
||||
} else if is_being_dragged && can_accept_what_is_being_dragged {
|
||||
ui.visuals().widgets.inactive
|
||||
} else if is_being_dragged && !can_accept_what_is_being_dragged {
|
||||
ui.visuals().widgets.disabled
|
||||
} else {
|
||||
ui.visuals().widgets.inactive
|
||||
};
|
||||
|
||||
let mut fill = style.bg_fill;
|
||||
let mut stroke = style.bg_stroke;
|
||||
if is_being_dragged && !can_accept_what_is_being_dragged {
|
||||
// gray out:
|
||||
fill = color::tint_color_towards(fill, ui.visuals().window_fill());
|
||||
stroke.color = color::tint_color_towards(stroke.color, ui.visuals().window_fill());
|
||||
}
|
||||
|
||||
ui.painter().set(
|
||||
where_to_put_background,
|
||||
Shape::Rect {
|
||||
corner_radius: style.corner_radius,
|
||||
fill: style.bg_fill,
|
||||
stroke: style.bg_stroke,
|
||||
fill,
|
||||
stroke,
|
||||
rect,
|
||||
},
|
||||
);
|
||||
|
|
|
@ -83,9 +83,12 @@ fn toggle_compact(ui: &mut egui::Ui, on: &mut bool) -> egui::Response {
|
|||
|
||||
pub fn demo(ui: &mut egui::Ui, on: &mut bool) {
|
||||
ui.horizontal_wrapped_for_text(egui::TextStyle::Button, |ui| {
|
||||
ui.label("It's easy to create your own widgets!");
|
||||
ui.label("This toggle switch is just one function and 15 lines of code:");
|
||||
toggle(ui, on).on_hover_text("Click to toggle");
|
||||
ui.add(crate::__egui_github_link_file!());
|
||||
});
|
||||
})
|
||||
.1
|
||||
.on_hover_text(
|
||||
"It's easy to create your own widgets!\n\
|
||||
This toggle switch is just one function and 20 lines of code.",
|
||||
);
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ enum Enum {
|
|||
|
||||
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct WidgetGallery {
|
||||
enabled: bool,
|
||||
boolean: bool,
|
||||
radio: Enum,
|
||||
scalar: f32,
|
||||
|
@ -18,6 +19,7 @@ pub struct WidgetGallery {
|
|||
impl Default for WidgetGallery {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
enabled: true,
|
||||
boolean: false,
|
||||
radio: Enum::First,
|
||||
scalar: 42.0,
|
||||
|
@ -46,6 +48,7 @@ impl super::Demo for WidgetGallery {
|
|||
impl super::View for WidgetGallery {
|
||||
fn ui(&mut self, ui: &mut egui::Ui) {
|
||||
let Self {
|
||||
enabled,
|
||||
boolean,
|
||||
radio,
|
||||
scalar,
|
||||
|
@ -53,6 +56,12 @@ impl super::View for WidgetGallery {
|
|||
color,
|
||||
} = self;
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.radio_value(enabled, true, "Enabled");
|
||||
ui.radio_value(enabled, false, "Disabled");
|
||||
});
|
||||
ui.set_enabled(*enabled);
|
||||
|
||||
let grid = egui::Grid::new("my_grid")
|
||||
.striped(true)
|
||||
.spacing([40.0, 4.0]);
|
||||
|
@ -65,7 +74,7 @@ impl super::View for WidgetGallery {
|
|||
ui.add(egui::Hyperlink::new("https://github.com/emilk/egui").text(" egui home page"));
|
||||
ui.end_row();
|
||||
|
||||
ui.label("Text Input:");
|
||||
ui.label("TextEdit:");
|
||||
ui.add(egui::TextEdit::singleline(string).hint_text("Write something here"));
|
||||
ui.end_row();
|
||||
|
||||
|
@ -73,7 +82,7 @@ impl super::View for WidgetGallery {
|
|||
ui.checkbox(boolean, "Checkbox");
|
||||
ui.end_row();
|
||||
|
||||
ui.label("Radio buttons:");
|
||||
ui.label("RadioButton:");
|
||||
ui.horizontal(|ui| {
|
||||
ui.radio_value(radio, Enum::First, "First");
|
||||
ui.radio_value(radio, Enum::Second, "Second");
|
||||
|
@ -143,6 +152,10 @@ impl super::View for WidgetGallery {
|
|||
});
|
||||
});
|
||||
ui.end_row();
|
||||
|
||||
ui.label("Custom widget");
|
||||
super::toggle_switch::demo(ui, boolean);
|
||||
ui.end_row();
|
||||
});
|
||||
|
||||
ui.separator();
|
||||
|
|
|
@ -134,8 +134,5 @@ impl Widgets {
|
|||
|
||||
ui.label("Multiline text input:");
|
||||
ui.text_edit_multiline(&mut self.multiline_text_input);
|
||||
|
||||
ui.separator();
|
||||
super::toggle_switch::demo(ui, &mut self.toggle_switch);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ pub struct WindowOptions {
|
|||
collapsible: bool,
|
||||
resizable: bool,
|
||||
scroll: bool,
|
||||
disabled_time: f64,
|
||||
}
|
||||
|
||||
impl Default for WindowOptions {
|
||||
|
@ -18,6 +19,7 @@ impl Default for WindowOptions {
|
|||
collapsible: true,
|
||||
resizable: true,
|
||||
scroll: false,
|
||||
disabled_time: f64::NEG_INFINITY,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -36,15 +38,22 @@ impl super::Demo for WindowOptions {
|
|||
collapsible,
|
||||
resizable,
|
||||
scroll,
|
||||
disabled_time,
|
||||
} = self.clone();
|
||||
|
||||
let enabled = ctx.input().time - disabled_time > 2.0;
|
||||
if !enabled {
|
||||
ctx.request_repaint();
|
||||
}
|
||||
|
||||
use super::View;
|
||||
let mut window = egui::Window::new(title)
|
||||
.id(egui::Id::new("demo_window_options")) // required since we change the title
|
||||
.resizable(resizable)
|
||||
.collapsible(collapsible)
|
||||
.title_bar(title_bar)
|
||||
.scroll(scroll);
|
||||
.scroll(scroll)
|
||||
.enabled(enabled);
|
||||
if closable {
|
||||
window = window.open(open);
|
||||
}
|
||||
|
@ -63,6 +72,7 @@ impl super::View for WindowOptions {
|
|||
collapsible,
|
||||
resizable,
|
||||
scroll,
|
||||
disabled_time,
|
||||
} = self;
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
|
@ -77,5 +87,9 @@ impl super::View for WindowOptions {
|
|||
ui.vertical_centered(|ui| {
|
||||
ui.add(crate::__egui_github_link_file!());
|
||||
});
|
||||
|
||||
if ui.button("Disable for 2 seconds").clicked() {
|
||||
*disabled_time = ui.input().time;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -682,3 +682,30 @@ impl From<Hsva> for HsvaGamma {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// Cheap and ugly.
|
||||
/// Made for graying out disabled `Ui`:s.
|
||||
pub fn tint_color_towards(color: Color32, target: Color32) -> Color32 {
|
||||
let [mut r, mut g, mut b, mut a] = color.to_array();
|
||||
|
||||
if a == 0 {
|
||||
r /= 2;
|
||||
g /= 2;
|
||||
b /= 2;
|
||||
} else if a < 170 {
|
||||
// Cheapish and looks ok.
|
||||
// Works for e.g. grid stripes.
|
||||
let div = (2 * 255 / a as i32) as u8;
|
||||
r = r / 2 + target.r() / div;
|
||||
g = g / 2 + target.g() / div;
|
||||
b = b / 2 + target.b() / div;
|
||||
a /= 2;
|
||||
} else {
|
||||
r = r / 2 + target.r() / 2;
|
||||
g = g / 2 + target.g() / 2;
|
||||
b = b / 2 + target.b() / 2;
|
||||
}
|
||||
Color32::from_rgba_premultiplied(r, g, b, a)
|
||||
}
|
||||
|
|
|
@ -49,6 +49,7 @@ mod mesh;
|
|||
pub mod mutex;
|
||||
mod shadow;
|
||||
mod shape;
|
||||
pub mod shape_transform;
|
||||
pub mod stats;
|
||||
mod stroke;
|
||||
pub mod tessellator;
|
||||
|
|
35
epaint/src/shape_transform.rs
Normal file
35
epaint/src/shape_transform.rs
Normal file
|
@ -0,0 +1,35 @@
|
|||
use crate::*;
|
||||
|
||||
pub fn adjust_colors(shape: &mut Shape, adjust_color: &impl Fn(&mut Color32)) {
|
||||
match shape {
|
||||
Shape::Noop => {}
|
||||
Shape::Vec(shapes) => {
|
||||
for shape in shapes {
|
||||
adjust_colors(shape, adjust_color)
|
||||
}
|
||||
}
|
||||
Shape::Circle { fill, stroke, .. } => {
|
||||
adjust_color(fill);
|
||||
adjust_color(&mut stroke.color);
|
||||
}
|
||||
Shape::LineSegment { stroke, .. } => {
|
||||
adjust_color(&mut stroke.color);
|
||||
}
|
||||
Shape::Path { fill, stroke, .. } => {
|
||||
adjust_color(fill);
|
||||
adjust_color(&mut stroke.color);
|
||||
}
|
||||
Shape::Rect { fill, stroke, .. } => {
|
||||
adjust_color(fill);
|
||||
adjust_color(&mut stroke.color);
|
||||
}
|
||||
Shape::Text { color, .. } => {
|
||||
adjust_color(color);
|
||||
}
|
||||
Shape::Mesh(mesh) => {
|
||||
for v in &mut mesh.vertices {
|
||||
adjust_color(&mut v.color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue