Merge branch 'master' into accesskit
This commit is contained in:
commit
125a7f0b1e
25 changed files with 973 additions and 409 deletions
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
|
@ -8,7 +8,7 @@ assignees: ''
|
||||||
---
|
---
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
First look if there is already a similar bug report. If there is, add a comment to it instead!
|
First look if there is already a similar bug report. If there is, upvote the issue with 👍
|
||||||
|
|
||||||
Please also check if the bug is still present in latest master! Do so by adding the following lines to your Cargo.toml:
|
Please also check if the bug is still present in latest master! Do so by adding the following lines to your Cargo.toml:
|
||||||
|
|
||||||
|
|
2
.github/ISSUE_TEMPLATE/feature_request.md
vendored
2
.github/ISSUE_TEMPLATE/feature_request.md
vendored
|
@ -8,7 +8,7 @@ assignees: ''
|
||||||
---
|
---
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
First look if there is already a similar feature request. If there is, add a comment to it instead!
|
First look if there is already a similar feature request. If there is, upvote the issue with 👍
|
||||||
-->
|
-->
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ NOTE: [`epaint`](crates/epaint/CHANGELOG.md), [`eframe`](crates/eframe/CHANGELOG
|
||||||
* MSRV (Minimum Supported Rust Version) is now `1.65.0` ([#2314](https://github.com/emilk/egui/pull/2314)).
|
* MSRV (Minimum Supported Rust Version) is now `1.65.0` ([#2314](https://github.com/emilk/egui/pull/2314)).
|
||||||
* ⚠️ BREAKING: egui now expects integrations to do all color blending in gamma space ([#2071](https://github.com/emilk/egui/pull/2071)).
|
* ⚠️ BREAKING: egui now expects integrations to do all color blending in gamma space ([#2071](https://github.com/emilk/egui/pull/2071)).
|
||||||
* ⚠️ BREAKING: if you have overlapping interactive widgets, only the top widget (last added) will be interactive ([#2244](https://github.com/emilk/egui/pull/2244)).
|
* ⚠️ BREAKING: if you have overlapping interactive widgets, only the top widget (last added) will be interactive ([#2244](https://github.com/emilk/egui/pull/2244)).
|
||||||
|
* Keyboard press events are only present at the frame when the key was pressed, consistent with how key releases work ([#2334](https://github.com/emilk/egui/pull/2334)).
|
||||||
|
|
||||||
### Added ⭐
|
### Added ⭐
|
||||||
* Added helper functions for animating panels that collapse/expand ([#2190](https://github.com/emilk/egui/pull/2190)).
|
* Added helper functions for animating panels that collapse/expand ([#2190](https://github.com/emilk/egui/pull/2190)).
|
||||||
|
@ -34,7 +35,8 @@ NOTE: [`epaint`](crates/epaint/CHANGELOG.md), [`eframe`](crates/eframe/CHANGELOG
|
||||||
* Improved text rendering ([#2071](https://github.com/emilk/egui/pull/2071)).
|
* Improved text rendering ([#2071](https://github.com/emilk/egui/pull/2071)).
|
||||||
* Less jitter when calling `Context::set_pixels_per_point` ([#2239](https://github.com/emilk/egui/pull/2239)).
|
* Less jitter when calling `Context::set_pixels_per_point` ([#2239](https://github.com/emilk/egui/pull/2239)).
|
||||||
* Fixed popups and color edit going outside the screen.
|
* Fixed popups and color edit going outside the screen.
|
||||||
* Fixed keyboard support in `DragValue`. ([#2342](https://github.com/emilk/egui/pull/2342)).
|
* Fixed keyboard support in `DragValue` ([#2342](https://github.com/emilk/egui/pull/2342)).
|
||||||
|
* If you nest `ScrollAreas` inside each other, the inner area will now move its scroll bar so it is always visible ([#2371](https://github.com/emilk/egui/pull/2371)).
|
||||||
|
|
||||||
|
|
||||||
## 0.19.0 - 2022-08-20
|
## 0.19.0 - 2022-08-20
|
||||||
|
|
8
Cargo.lock
generated
8
Cargo.lock
generated
|
@ -2209,6 +2209,14 @@ dependencies = [
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "keyboard_events"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"eframe",
|
||||||
|
"tracing-subscriber",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "khronos-egl"
|
name = "khronos-egl"
|
||||||
version = "4.1.0"
|
version = "4.1.0"
|
||||||
|
|
|
@ -215,6 +215,7 @@ impl SidePanel {
|
||||||
}
|
}
|
||||||
width = clamp_to_range(width, width_range.clone()).at_most(available_rect.width());
|
width = clamp_to_range(width, width_range.clone()).at_most(available_rect.width());
|
||||||
side.set_rect_width(&mut panel_rect, width);
|
side.set_rect_width(&mut panel_rect, width);
|
||||||
|
ui.ctx().check_for_id_clash(id, panel_rect, "SidePanel");
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut resize_hover = false;
|
let mut resize_hover = false;
|
||||||
|
@ -642,6 +643,8 @@ impl TopBottomPanel {
|
||||||
};
|
};
|
||||||
height = clamp_to_range(height, height_range.clone()).at_most(available_rect.height());
|
height = clamp_to_range(height, height_range.clone()).at_most(available_rect.height());
|
||||||
side.set_rect_height(&mut panel_rect, height);
|
side.set_rect_height(&mut panel_rect, height);
|
||||||
|
ui.ctx()
|
||||||
|
.check_for_id_clash(id, panel_rect, "TopBottomPanel");
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut resize_hover = false;
|
let mut resize_hover = false;
|
||||||
|
|
|
@ -66,6 +66,10 @@ pub struct ScrollAreaOutput<R> {
|
||||||
/// The current state of the scroll area.
|
/// The current state of the scroll area.
|
||||||
pub state: State,
|
pub state: State,
|
||||||
|
|
||||||
|
/// The size of the content. If this is larger than [`Self::inner_rect`],
|
||||||
|
/// then there was need for scrolling.
|
||||||
|
pub content_size: Vec2,
|
||||||
|
|
||||||
/// Where on the screen the content is (excludes scroll bars).
|
/// Where on the screen the content is (excludes scroll bars).
|
||||||
pub inner_rect: Rect,
|
pub inner_rect: Rect,
|
||||||
}
|
}
|
||||||
|
@ -198,6 +202,8 @@ impl ScrollArea {
|
||||||
|
|
||||||
/// Set the horizontal and vertical scroll offset position.
|
/// Set the horizontal and vertical scroll offset position.
|
||||||
///
|
///
|
||||||
|
/// Positive offset means scrolling down/right.
|
||||||
|
///
|
||||||
/// See also: [`Self::vertical_scroll_offset`], [`Self::horizontal_scroll_offset`],
|
/// See also: [`Self::vertical_scroll_offset`], [`Self::horizontal_scroll_offset`],
|
||||||
/// [`Ui::scroll_to_cursor`](crate::ui::Ui::scroll_to_cursor) and
|
/// [`Ui::scroll_to_cursor`](crate::ui::Ui::scroll_to_cursor) and
|
||||||
/// [`Response::scroll_to_me`](crate::Response::scroll_to_me)
|
/// [`Response::scroll_to_me`](crate::Response::scroll_to_me)
|
||||||
|
@ -209,6 +215,8 @@ impl ScrollArea {
|
||||||
|
|
||||||
/// Set the vertical scroll offset position.
|
/// Set the vertical scroll offset position.
|
||||||
///
|
///
|
||||||
|
/// Positive offset means scrolling down.
|
||||||
|
///
|
||||||
/// See also: [`Self::scroll_offset`], [`Ui::scroll_to_cursor`](crate::ui::Ui::scroll_to_cursor) and
|
/// See also: [`Self::scroll_offset`], [`Ui::scroll_to_cursor`](crate::ui::Ui::scroll_to_cursor) and
|
||||||
/// [`Response::scroll_to_me`](crate::Response::scroll_to_me)
|
/// [`Response::scroll_to_me`](crate::Response::scroll_to_me)
|
||||||
pub fn vertical_scroll_offset(mut self, offset: f32) -> Self {
|
pub fn vertical_scroll_offset(mut self, offset: f32) -> Self {
|
||||||
|
@ -218,6 +226,8 @@ impl ScrollArea {
|
||||||
|
|
||||||
/// Set the horizontal scroll offset position.
|
/// Set the horizontal scroll offset position.
|
||||||
///
|
///
|
||||||
|
/// Positive offset means scrolling right.
|
||||||
|
///
|
||||||
/// See also: [`Self::scroll_offset`], [`Ui::scroll_to_cursor`](crate::ui::Ui::scroll_to_cursor) and
|
/// See also: [`Self::scroll_offset`], [`Ui::scroll_to_cursor`](crate::ui::Ui::scroll_to_cursor) and
|
||||||
/// [`Response::scroll_to_me`](crate::Response::scroll_to_me)
|
/// [`Response::scroll_to_me`](crate::Response::scroll_to_me)
|
||||||
pub fn horizontal_scroll_offset(mut self, offset: f32) -> Self {
|
pub fn horizontal_scroll_offset(mut self, offset: f32) -> Self {
|
||||||
|
@ -541,18 +551,20 @@ impl ScrollArea {
|
||||||
let id = prepared.id;
|
let id = prepared.id;
|
||||||
let inner_rect = prepared.inner_rect;
|
let inner_rect = prepared.inner_rect;
|
||||||
let inner = add_contents(&mut prepared.content_ui, prepared.viewport);
|
let inner = add_contents(&mut prepared.content_ui, prepared.viewport);
|
||||||
let state = prepared.end(ui);
|
let (content_size, state) = prepared.end(ui);
|
||||||
ScrollAreaOutput {
|
ScrollAreaOutput {
|
||||||
inner,
|
inner,
|
||||||
id,
|
id,
|
||||||
state,
|
state,
|
||||||
|
content_size,
|
||||||
inner_rect,
|
inner_rect,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Prepared {
|
impl Prepared {
|
||||||
fn end(self, ui: &mut Ui) -> State {
|
/// Returns content size and state
|
||||||
|
fn end(self, ui: &mut Ui) -> (Vec2, State) {
|
||||||
let Prepared {
|
let Prepared {
|
||||||
id,
|
id,
|
||||||
mut state,
|
mut state,
|
||||||
|
@ -623,23 +635,7 @@ impl Prepared {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut inner_rect = Rect::from_min_size(inner_rect.min, inner_size);
|
Rect::from_min_size(inner_rect.min, inner_size)
|
||||||
|
|
||||||
// The window that egui sits in can't be expanded by egui, so we need to respect it:
|
|
||||||
for d in 0..2 {
|
|
||||||
if !has_bar[d] {
|
|
||||||
// HACK for when we have a vertical-only scroll area in a top level panel,
|
|
||||||
// and that panel is not wide enough for the contents.
|
|
||||||
// This code ensures we still see the scroll bar!
|
|
||||||
let max = ui.input().screen_rect().max[d]
|
|
||||||
- current_bar_use[d]
|
|
||||||
- ui.spacing().item_spacing[d];
|
|
||||||
inner_rect.max[d] = inner_rect.max[d].at_most(max);
|
|
||||||
// TODO(emilk): maybe auto-enable horizontal/vertical scrolling if this limit is reached
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inner_rect
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let outer_rect = Rect::from_min_size(inner_rect.min, inner_rect.size() + current_bar_use);
|
let outer_rect = Rect::from_min_size(inner_rect.min, inner_rect.size() + current_bar_use);
|
||||||
|
@ -691,13 +687,29 @@ impl Prepared {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// margin between contents and scroll bar
|
// margin on either side of the scroll bar
|
||||||
let margin = animation_t * ui.spacing().item_spacing.x;
|
let inner_margin = animation_t * ui.spacing().scroll_bar_inner_margin;
|
||||||
let min_cross = inner_rect.max[1 - d] + margin; // left of vertical scroll (d == 1)
|
let outer_margin = animation_t * ui.spacing().scroll_bar_outer_margin;
|
||||||
let max_cross = outer_rect.max[1 - d]; // right of vertical scroll (d == 1)
|
let mut min_cross = inner_rect.max[1 - d] + inner_margin; // left of vertical scroll (d == 1)
|
||||||
|
let mut max_cross = outer_rect.max[1 - d] - outer_margin; // right of vertical scroll (d == 1)
|
||||||
let min_main = inner_rect.min[d]; // top of vertical scroll (d == 1)
|
let min_main = inner_rect.min[d]; // top of vertical scroll (d == 1)
|
||||||
let max_main = inner_rect.max[d]; // bottom of vertical scroll (d == 1)
|
let max_main = inner_rect.max[d]; // bottom of vertical scroll (d == 1)
|
||||||
|
|
||||||
|
if ui.clip_rect().max[1 - d] < max_cross + outer_margin {
|
||||||
|
// Move the scrollbar so it is visible. This is needed in some cases.
|
||||||
|
// For instance:
|
||||||
|
// * When we have a vertical-only scroll area in a top level panel,
|
||||||
|
// and that panel is not wide enough for the contents.
|
||||||
|
// * When one ScrollArea is nested inside another, and the outer
|
||||||
|
// is scrolled so that the scroll-bars of the inner ScrollArea (us)
|
||||||
|
// is outside the clip rectangle.
|
||||||
|
// Really this should use the tighter clip_rect that ignores clip_rect_margin, but we don't store that.
|
||||||
|
// clip_rect_margin is quite a hack. It would be nice to get rid of it.
|
||||||
|
let width = max_cross - min_cross;
|
||||||
|
max_cross = ui.clip_rect().max[1 - d] - outer_margin;
|
||||||
|
min_cross = max_cross - width;
|
||||||
|
}
|
||||||
|
|
||||||
let outer_scroll_rect = if d == 0 {
|
let outer_scroll_rect = if d == 0 {
|
||||||
Rect::from_min_max(
|
Rect::from_min_max(
|
||||||
pos2(inner_rect.left(), min_cross),
|
pos2(inner_rect.left(), min_cross),
|
||||||
|
@ -847,11 +859,13 @@ impl Prepared {
|
||||||
|
|
||||||
state.store(ui.ctx(), id);
|
state.store(ui.ctx(), id);
|
||||||
|
|
||||||
state
|
(content_size, state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Width of a vertical scrollbar, or height of a horizontal scroll bar
|
/// Width of a vertical scrollbar, or height of a horizontal scroll bar
|
||||||
fn max_scroll_bar_width_with_margin(ui: &Ui) -> f32 {
|
fn max_scroll_bar_width_with_margin(ui: &Ui) -> f32 {
|
||||||
ui.spacing().item_spacing.x + ui.spacing().scroll_bar_width
|
ui.spacing().scroll_bar_inner_margin
|
||||||
|
+ ui.spacing().scroll_bar_width
|
||||||
|
+ ui.spacing().scroll_bar_outer_margin
|
||||||
}
|
}
|
||||||
|
|
|
@ -319,16 +319,45 @@ impl Context {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let show_error = |pos: Pos2, text: String| {
|
let show_error = |widget_rect: Rect, text: String| {
|
||||||
|
let text = format!("🔥 {}", text);
|
||||||
|
let color = self.style().visuals.error_fg_color;
|
||||||
let painter = self.debug_painter();
|
let painter = self.debug_painter();
|
||||||
let rect = painter.error(pos, text);
|
painter.rect_stroke(widget_rect, 0.0, (1.0, color));
|
||||||
|
|
||||||
|
let below = widget_rect.bottom() + 32.0 < self.input().screen_rect.bottom();
|
||||||
|
|
||||||
|
let text_rect = if below {
|
||||||
|
painter.debug_text(
|
||||||
|
widget_rect.left_bottom() + vec2(0.0, 2.0),
|
||||||
|
Align2::LEFT_TOP,
|
||||||
|
color,
|
||||||
|
text,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
painter.debug_text(
|
||||||
|
widget_rect.left_top() - vec2(0.0, 2.0),
|
||||||
|
Align2::LEFT_BOTTOM,
|
||||||
|
color,
|
||||||
|
text,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
if let Some(pointer_pos) = self.pointer_hover_pos() {
|
if let Some(pointer_pos) = self.pointer_hover_pos() {
|
||||||
if rect.contains(pointer_pos) {
|
if text_rect.contains(pointer_pos) {
|
||||||
|
let tooltip_pos = if below {
|
||||||
|
text_rect.left_bottom() + vec2(2.0, 4.0)
|
||||||
|
} else {
|
||||||
|
text_rect.left_top() + vec2(2.0, -4.0)
|
||||||
|
};
|
||||||
|
|
||||||
painter.error(
|
painter.error(
|
||||||
rect.left_bottom() + vec2(2.0, 4.0),
|
tooltip_pos,
|
||||||
"ID clashes happens when things like Windows or CollapsingHeaders share names,\n\
|
format!("Widget is {} this text.\n\n\
|
||||||
|
ID clashes happens when things like Windows or CollapsingHeaders share names,\n\
|
||||||
or when things like Plot and Grid:s aren't given unique id_source:s.\n\n\
|
or when things like Plot and Grid:s aren't given unique id_source:s.\n\n\
|
||||||
Sometimes the solution is to use ui.push_id.",
|
Sometimes the solution is to use ui.push_id.",
|
||||||
|
if below { "above" } else { "below" })
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -337,19 +366,10 @@ impl Context {
|
||||||
let id_str = id.short_debug_format();
|
let id_str = id.short_debug_format();
|
||||||
|
|
||||||
if prev_rect.min.distance(new_rect.min) < 4.0 {
|
if prev_rect.min.distance(new_rect.min) < 4.0 {
|
||||||
show_error(
|
show_error(new_rect, format!("Double use of {} ID {}", what, id_str));
|
||||||
new_rect.min,
|
|
||||||
format!("Double use of {} ID {}", what, id_str),
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
show_error(
|
show_error(prev_rect, format!("First use of {} ID {}", what, id_str));
|
||||||
prev_rect.min,
|
show_error(new_rect, format!("Second use of {} ID {}", what, id_str));
|
||||||
format!("First use of {} ID {}", what, id_str),
|
|
||||||
);
|
|
||||||
show_error(
|
|
||||||
new_rect.min,
|
|
||||||
format!("Second use of {} ID {}", what, id_str),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -138,7 +138,11 @@ impl Default for InputState {
|
||||||
|
|
||||||
impl InputState {
|
impl InputState {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn begin_frame(mut self, new: RawInput, requested_repaint_last_frame: bool) -> InputState {
|
pub fn begin_frame(
|
||||||
|
mut self,
|
||||||
|
mut new: RawInput,
|
||||||
|
requested_repaint_last_frame: bool,
|
||||||
|
) -> InputState {
|
||||||
let time = new.time.unwrap_or(self.time + new.predicted_dt as f64);
|
let time = new.time.unwrap_or(self.time + new.predicted_dt as f64);
|
||||||
let unstable_dt = (time - self.time) as f32;
|
let unstable_dt = (time - self.time) as f32;
|
||||||
|
|
||||||
|
@ -160,24 +164,26 @@ impl InputState {
|
||||||
let mut keys_down = self.keys_down;
|
let mut keys_down = self.keys_down;
|
||||||
let mut scroll_delta = Vec2::ZERO;
|
let mut scroll_delta = Vec2::ZERO;
|
||||||
let mut zoom_factor_delta = 1.0;
|
let mut zoom_factor_delta = 1.0;
|
||||||
for event in &new.events {
|
new.events.retain(|event| match event {
|
||||||
match event {
|
Event::Key { key, pressed, .. } => {
|
||||||
Event::Key { key, pressed, .. } => {
|
if *pressed {
|
||||||
if *pressed {
|
// We only retain presses that are novel (i.e. the first Press event, not those generated by key-repeat)
|
||||||
keys_down.insert(*key);
|
keys_down.insert(*key)
|
||||||
} else {
|
} else {
|
||||||
keys_down.remove(key);
|
keys_down.remove(key);
|
||||||
}
|
true
|
||||||
}
|
}
|
||||||
Event::Scroll(delta) => {
|
|
||||||
scroll_delta += *delta;
|
|
||||||
}
|
|
||||||
Event::Zoom(factor) => {
|
|
||||||
zoom_factor_delta *= *factor;
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
}
|
Event::Scroll(delta) => {
|
||||||
|
scroll_delta += *delta;
|
||||||
|
true
|
||||||
|
}
|
||||||
|
Event::Zoom(factor) => {
|
||||||
|
zoom_factor_delta *= *factor;
|
||||||
|
true
|
||||||
|
}
|
||||||
|
_ => true,
|
||||||
|
});
|
||||||
InputState {
|
InputState {
|
||||||
pointer,
|
pointer,
|
||||||
touch_states: self.touch_states,
|
touch_states: self.touch_states,
|
||||||
|
@ -285,7 +291,7 @@ impl InputState {
|
||||||
self.num_presses(desired_key) > 0
|
self.num_presses(desired_key) > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
/// How many times were the given key pressed this frame?
|
/// How many times was the given key pressed this frame?
|
||||||
pub fn num_presses(&self, desired_key: Key) -> usize {
|
pub fn num_presses(&self, desired_key: Key) -> usize {
|
||||||
self.events
|
self.events
|
||||||
.iter()
|
.iter()
|
||||||
|
|
|
@ -229,22 +229,10 @@ impl Layout {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// For when you want to add a single widget to a layout, and that widget
|
|
||||||
/// should be centered horizontally and vertically.
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn centered(main_dir: Direction) -> Self {
|
|
||||||
Self {
|
|
||||||
main_dir,
|
|
||||||
main_wrap: false,
|
|
||||||
main_align: Align::Center,
|
|
||||||
main_justify: false,
|
|
||||||
cross_align: Align::Center,
|
|
||||||
cross_justify: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// For when you want to add a single widget to a layout, and that widget
|
/// For when you want to add a single widget to a layout, and that widget
|
||||||
/// should use up all available space.
|
/// should use up all available space.
|
||||||
|
///
|
||||||
|
/// Only one widget may be added to the inner `Ui`!
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn centered_and_justified(main_dir: Direction) -> Self {
|
pub fn centered_and_justified(main_dir: Direction) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
|
|
@ -208,7 +208,12 @@ impl Painter {
|
||||||
impl Painter {
|
impl Painter {
|
||||||
#[allow(clippy::needless_pass_by_value)]
|
#[allow(clippy::needless_pass_by_value)]
|
||||||
pub fn debug_rect(&self, rect: Rect, color: Color32, text: impl ToString) {
|
pub fn debug_rect(&self, rect: Rect, color: Color32, text: impl ToString) {
|
||||||
self.rect_stroke(rect, 0.0, (1.0, color));
|
self.rect(
|
||||||
|
rect,
|
||||||
|
0.0,
|
||||||
|
color.additive().linear_multiply(0.015),
|
||||||
|
(1.0, color),
|
||||||
|
);
|
||||||
self.text(
|
self.text(
|
||||||
rect.min,
|
rect.min,
|
||||||
Align2::LEFT_TOP,
|
Align2::LEFT_TOP,
|
||||||
|
@ -238,7 +243,7 @@ impl Painter {
|
||||||
self.add(Shape::rect_filled(
|
self.add(Shape::rect_filled(
|
||||||
frame_rect,
|
frame_rect,
|
||||||
0.0,
|
0.0,
|
||||||
Color32::from_black_alpha(240),
|
Color32::from_black_alpha(150),
|
||||||
));
|
));
|
||||||
self.galley(rect.min, galley);
|
self.galley(rect.min, galley);
|
||||||
frame_rect
|
frame_rect
|
||||||
|
|
|
@ -292,6 +292,11 @@ pub struct Spacing {
|
||||||
pub combo_height: f32,
|
pub combo_height: f32,
|
||||||
|
|
||||||
pub scroll_bar_width: f32,
|
pub scroll_bar_width: f32,
|
||||||
|
|
||||||
|
/// Margin between contents and scroll bar.
|
||||||
|
pub scroll_bar_inner_margin: f32,
|
||||||
|
/// Margin between scroll bar and the outer container (e.g. right of a vertical scroll bar).
|
||||||
|
pub scroll_bar_outer_margin: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Spacing {
|
impl Spacing {
|
||||||
|
@ -658,6 +663,8 @@ impl Default for Spacing {
|
||||||
tooltip_width: 600.0,
|
tooltip_width: 600.0,
|
||||||
combo_height: 200.0,
|
combo_height: 200.0,
|
||||||
scroll_bar_width: 8.0,
|
scroll_bar_width: 8.0,
|
||||||
|
scroll_bar_inner_margin: 4.0,
|
||||||
|
scroll_bar_outer_margin: 0.0,
|
||||||
indent_ends_with_horizontal_line: false,
|
indent_ends_with_horizontal_line: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -941,6 +948,8 @@ impl Spacing {
|
||||||
indent_ends_with_horizontal_line,
|
indent_ends_with_horizontal_line,
|
||||||
combo_height,
|
combo_height,
|
||||||
scroll_bar_width,
|
scroll_bar_width,
|
||||||
|
scroll_bar_inner_margin,
|
||||||
|
scroll_bar_outer_margin,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
ui.add(slider_vec2(item_spacing, 0.0..=20.0, "Item spacing"));
|
ui.add(slider_vec2(item_spacing, 0.0..=20.0, "Item spacing"));
|
||||||
|
@ -1021,7 +1030,15 @@ impl Spacing {
|
||||||
});
|
});
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.add(DragValue::new(scroll_bar_width).clamp_range(0.0..=32.0));
|
ui.add(DragValue::new(scroll_bar_width).clamp_range(0.0..=32.0));
|
||||||
ui.label("Scroll-bar width width");
|
ui.label("Scroll-bar width");
|
||||||
|
});
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.add(DragValue::new(scroll_bar_inner_margin).clamp_range(0.0..=32.0));
|
||||||
|
ui.label("Scroll-bar inner margin");
|
||||||
|
});
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.add(DragValue::new(scroll_bar_outer_margin).clamp_range(0.0..=32.0));
|
||||||
|
ui.label("Scroll-bar outer margin");
|
||||||
});
|
});
|
||||||
|
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
|
|
|
@ -2007,12 +2007,14 @@ impl Ui {
|
||||||
InnerResponse::new(inner, self.interact(rect, child_ui.id, Sense::hover()))
|
InnerResponse::new(inner, self.interact(rect, child_ui.id, Sense::hover()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This will make the next added widget centered in the available space.
|
#[deprecated = "Use ui.vertical_centered instead"]
|
||||||
pub fn centered<R>(&mut self, add_contents: impl FnOnce(&mut Self) -> R) -> InnerResponse<R> {
|
pub fn centered<R>(&mut self, add_contents: impl FnOnce(&mut Self) -> R) -> InnerResponse<R> {
|
||||||
self.with_layout_dyn(Layout::centered(Direction::TopDown), Box::new(add_contents))
|
self.vertical_centered(add_contents)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This will make the next added widget centered and justified in the available space.
|
/// This will make the next added widget centered and justified in the available space.
|
||||||
|
///
|
||||||
|
/// Only one widget may be added to the inner `Ui`!
|
||||||
pub fn centered_and_justified<R>(
|
pub fn centered_and_justified<R>(
|
||||||
&mut self,
|
&mut self,
|
||||||
add_contents: impl FnOnce(&mut Self) -> R,
|
add_contents: impl FnOnce(&mut Self) -> R,
|
||||||
|
|
|
@ -20,8 +20,8 @@ default = ["glow", "persistence"]
|
||||||
|
|
||||||
http = ["ehttp", "image", "poll-promise", "egui_extras/image"]
|
http = ["ehttp", "image", "poll-promise", "egui_extras/image"]
|
||||||
persistence = ["eframe/persistence", "egui/persistence", "serde"]
|
persistence = ["eframe/persistence", "egui/persistence", "serde"]
|
||||||
screen_reader = ["eframe/screen_reader"] # experimental
|
screen_reader = ["eframe/screen_reader"] # experimental
|
||||||
serde = ["dep:serde", "egui_demo_lib/serde", "egui_extras/serde", "egui/serde"]
|
serde = ["dep:serde", "egui_demo_lib/serde", "egui/serde"]
|
||||||
syntax_highlighting = ["egui_demo_lib/syntax_highlighting"]
|
syntax_highlighting = ["egui_demo_lib/syntax_highlighting"]
|
||||||
|
|
||||||
glow = ["eframe/glow"]
|
glow = ["eframe/glow"]
|
||||||
|
|
|
@ -7,6 +7,7 @@ enum ScrollDemo {
|
||||||
ManyLines,
|
ManyLines,
|
||||||
LargeCanvas,
|
LargeCanvas,
|
||||||
StickToEnd,
|
StickToEnd,
|
||||||
|
Bidirectional,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ScrollDemo {
|
impl Default for ScrollDemo {
|
||||||
|
@ -55,6 +56,7 @@ impl super::View for Scrolling {
|
||||||
"Scroll a large canvas",
|
"Scroll a large canvas",
|
||||||
);
|
);
|
||||||
ui.selectable_value(&mut self.demo, ScrollDemo::StickToEnd, "Stick to end");
|
ui.selectable_value(&mut self.demo, ScrollDemo::StickToEnd, "Stick to end");
|
||||||
|
ui.selectable_value(&mut self.demo, ScrollDemo::Bidirectional, "Bidirectional");
|
||||||
});
|
});
|
||||||
ui.separator();
|
ui.separator();
|
||||||
match self.demo {
|
match self.demo {
|
||||||
|
@ -70,6 +72,14 @@ impl super::View for Scrolling {
|
||||||
ScrollDemo::StickToEnd => {
|
ScrollDemo::StickToEnd => {
|
||||||
self.scroll_stick_to.ui(ui);
|
self.scroll_stick_to.ui(ui);
|
||||||
}
|
}
|
||||||
|
ScrollDemo::Bidirectional => {
|
||||||
|
egui::ScrollArea::both().show(ui, |ui| {
|
||||||
|
ui.style_mut().wrap = Some(false);
|
||||||
|
for _ in 0..100 {
|
||||||
|
ui.label(crate::LOREM_IPSUM);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,20 +10,22 @@ enum DemoType {
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||||
pub struct TableDemo {
|
pub struct TableDemo {
|
||||||
demo: DemoType,
|
demo: DemoType,
|
||||||
|
striped: bool,
|
||||||
resizable: bool,
|
resizable: bool,
|
||||||
num_rows: usize,
|
num_rows: usize,
|
||||||
row_to_scroll_to: i32,
|
scroll_to_row_slider: usize,
|
||||||
vertical_scroll_offset: Option<f32>,
|
scroll_to_row: Option<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for TableDemo {
|
impl Default for TableDemo {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
demo: DemoType::Manual,
|
demo: DemoType::Manual,
|
||||||
|
striped: true,
|
||||||
resizable: true,
|
resizable: true,
|
||||||
num_rows: 10_000,
|
num_rows: 10_000,
|
||||||
row_to_scroll_to: 0,
|
scroll_to_row_slider: 0,
|
||||||
vertical_scroll_offset: None,
|
scroll_to_row: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,16 +47,15 @@ impl super::Demo for TableDemo {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn scroll_offset_for_row(ui: &egui::Ui, row: i32) -> f32 {
|
const NUM_MANUAL_ROWS: usize = 20;
|
||||||
let text_height = egui::TextStyle::Body.resolve(ui.style()).size;
|
|
||||||
let row_item_spacing = ui.spacing().item_spacing.y;
|
|
||||||
row as f32 * (text_height + row_item_spacing)
|
|
||||||
}
|
|
||||||
|
|
||||||
impl super::View for TableDemo {
|
impl super::View for TableDemo {
|
||||||
fn ui(&mut self, ui: &mut egui::Ui) {
|
fn ui(&mut self, ui: &mut egui::Ui) {
|
||||||
ui.vertical(|ui| {
|
ui.vertical(|ui| {
|
||||||
ui.checkbox(&mut self.resizable, "Resizable columns");
|
ui.horizontal(|ui| {
|
||||||
|
ui.checkbox(&mut self.striped, "Striped");
|
||||||
|
ui.checkbox(&mut self.resizable, "Resizable columns");
|
||||||
|
});
|
||||||
|
|
||||||
ui.label("Table type:");
|
ui.label("Table type:");
|
||||||
ui.radio_value(&mut self.demo, DemoType::Manual, "Few, manual rows");
|
ui.radio_value(&mut self.demo, DemoType::Manual, "Few, manual rows");
|
||||||
|
@ -77,16 +78,20 @@ impl super::View for TableDemo {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.demo == DemoType::ManyHomogenous {
|
{
|
||||||
ui.add(
|
let max_rows = if self.demo == DemoType::Manual {
|
||||||
egui::Slider::new(&mut self.row_to_scroll_to, 0..=self.num_rows as i32)
|
NUM_MANUAL_ROWS
|
||||||
|
} else {
|
||||||
|
self.num_rows
|
||||||
|
};
|
||||||
|
|
||||||
|
let slider_response = ui.add(
|
||||||
|
egui::Slider::new(&mut self.scroll_to_row_slider, 0..=max_rows)
|
||||||
.logarithmic(true)
|
.logarithmic(true)
|
||||||
.text("Row to scroll to"),
|
.text("Row to scroll to"),
|
||||||
);
|
);
|
||||||
|
if slider_response.changed() {
|
||||||
if ui.button("Scroll to row").clicked() {
|
self.scroll_to_row = Some(self.scroll_to_row_slider);
|
||||||
self.vertical_scroll_offset
|
|
||||||
.replace(scroll_offset_for_row(ui, self.row_to_scroll_to));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -96,11 +101,13 @@ impl super::View for TableDemo {
|
||||||
// Leave room for the source code link after the table demo:
|
// Leave room for the source code link after the table demo:
|
||||||
use egui_extras::{Size, StripBuilder};
|
use egui_extras::{Size, StripBuilder};
|
||||||
StripBuilder::new(ui)
|
StripBuilder::new(ui)
|
||||||
.size(Size::remainder()) // for the table
|
.size(Size::remainder().at_least(100.0)) // for the table
|
||||||
.size(Size::exact(10.0)) // for the source code link
|
.size(Size::exact(10.0)) // for the source code link
|
||||||
.vertical(|mut strip| {
|
.vertical(|mut strip| {
|
||||||
strip.cell(|ui| {
|
strip.cell(|ui| {
|
||||||
self.table_ui(ui);
|
egui::ScrollArea::horizontal().show(ui, |ui| {
|
||||||
|
self.table_ui(ui);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
strip.cell(|ui| {
|
strip.cell(|ui| {
|
||||||
ui.vertical_centered(|ui| {
|
ui.vertical_centered(|ui| {
|
||||||
|
@ -113,37 +120,46 @@ impl super::View for TableDemo {
|
||||||
|
|
||||||
impl TableDemo {
|
impl TableDemo {
|
||||||
fn table_ui(&mut self, ui: &mut egui::Ui) {
|
fn table_ui(&mut self, ui: &mut egui::Ui) {
|
||||||
use egui_extras::{Size, TableBuilder};
|
use egui_extras::{Column, TableBuilder};
|
||||||
|
|
||||||
let text_height = egui::TextStyle::Body.resolve(ui.style()).size;
|
let text_height = egui::TextStyle::Body.resolve(ui.style()).size;
|
||||||
|
|
||||||
let mut table = TableBuilder::new(ui)
|
let mut table = TableBuilder::new(ui)
|
||||||
.striped(true)
|
.striped(self.striped)
|
||||||
.cell_layout(egui::Layout::left_to_right(egui::Align::Center))
|
.cell_layout(egui::Layout::left_to_right(egui::Align::Center))
|
||||||
.column(Size::initial(60.0).at_least(40.0))
|
.column(Column::auto())
|
||||||
.column(Size::initial(60.0).at_least(40.0))
|
.column(Column::initial(100.0).range(40.0..=300.0).resizable(true))
|
||||||
.column(Size::remainder().at_least(60.0))
|
.column(
|
||||||
.resizable(self.resizable);
|
Column::initial(100.0)
|
||||||
|
.at_least(40.0)
|
||||||
|
.resizable(true)
|
||||||
|
.clip(true),
|
||||||
|
)
|
||||||
|
.column(Column::remainder())
|
||||||
|
.min_scrolled_height(0.0);
|
||||||
|
|
||||||
if let Some(y_scroll) = self.vertical_scroll_offset.take() {
|
if let Some(row_nr) = self.scroll_to_row.take() {
|
||||||
table = table.vertical_scroll_offset(y_scroll);
|
table = table.scroll_to_row(row_nr, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
table
|
table
|
||||||
.header(20.0, |mut header| {
|
.header(20.0, |mut header| {
|
||||||
header.col(|ui| {
|
header.col(|ui| {
|
||||||
ui.heading("Row");
|
ui.strong("Row");
|
||||||
});
|
});
|
||||||
header.col(|ui| {
|
header.col(|ui| {
|
||||||
ui.heading("Clock");
|
ui.strong("Expanding content");
|
||||||
});
|
});
|
||||||
header.col(|ui| {
|
header.col(|ui| {
|
||||||
ui.heading("Content");
|
ui.strong("Clipped text");
|
||||||
|
});
|
||||||
|
header.col(|ui| {
|
||||||
|
ui.strong("Content");
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.body(|mut body| match self.demo {
|
.body(|mut body| match self.demo {
|
||||||
DemoType::Manual => {
|
DemoType::Manual => {
|
||||||
for row_index in 0..20 {
|
for row_index in 0..NUM_MANUAL_ROWS {
|
||||||
let is_thick = thick_row(row_index);
|
let is_thick = thick_row(row_index);
|
||||||
let row_height = if is_thick { 30.0 } else { 18.0 };
|
let row_height = if is_thick { 30.0 } else { 18.0 };
|
||||||
body.row(row_height, |mut row| {
|
body.row(row_height, |mut row| {
|
||||||
|
@ -151,7 +167,10 @@ impl TableDemo {
|
||||||
ui.label(row_index.to_string());
|
ui.label(row_index.to_string());
|
||||||
});
|
});
|
||||||
row.col(|ui| {
|
row.col(|ui| {
|
||||||
ui.label(clock_emoji(row_index));
|
expanding_content(ui);
|
||||||
|
});
|
||||||
|
row.col(|ui| {
|
||||||
|
ui.label(long_text(row_index));
|
||||||
});
|
});
|
||||||
row.col(|ui| {
|
row.col(|ui| {
|
||||||
ui.style_mut().wrap = Some(false);
|
ui.style_mut().wrap = Some(false);
|
||||||
|
@ -170,7 +189,10 @@ impl TableDemo {
|
||||||
ui.label(row_index.to_string());
|
ui.label(row_index.to_string());
|
||||||
});
|
});
|
||||||
row.col(|ui| {
|
row.col(|ui| {
|
||||||
ui.label(clock_emoji(row_index));
|
expanding_content(ui);
|
||||||
|
});
|
||||||
|
row.col(|ui| {
|
||||||
|
ui.label(long_text(row_index));
|
||||||
});
|
});
|
||||||
row.col(|ui| {
|
row.col(|ui| {
|
||||||
ui.add(
|
ui.add(
|
||||||
|
@ -191,24 +213,21 @@ impl TableDemo {
|
||||||
(0..self.num_rows).into_iter().map(row_thickness),
|
(0..self.num_rows).into_iter().map(row_thickness),
|
||||||
|row_index, mut row| {
|
|row_index, mut row| {
|
||||||
row.col(|ui| {
|
row.col(|ui| {
|
||||||
ui.centered_and_justified(|ui| {
|
ui.label(row_index.to_string());
|
||||||
ui.label(row_index.to_string());
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
row.col(|ui| {
|
row.col(|ui| {
|
||||||
ui.centered_and_justified(|ui| {
|
expanding_content(ui);
|
||||||
ui.label(clock_emoji(row_index));
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
row.col(|ui| {
|
row.col(|ui| {
|
||||||
ui.centered_and_justified(|ui| {
|
ui.label(long_text(row_index));
|
||||||
ui.style_mut().wrap = Some(false);
|
});
|
||||||
if thick_row(row_index) {
|
row.col(|ui| {
|
||||||
ui.heading("Extra thick row");
|
ui.style_mut().wrap = Some(false);
|
||||||
} else {
|
if thick_row(row_index) {
|
||||||
ui.label("Normal row");
|
ui.heading("Extra thick row");
|
||||||
}
|
} else {
|
||||||
});
|
ui.label("Normal row");
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -217,10 +236,19 @@ impl TableDemo {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clock_emoji(row_index: usize) -> String {
|
fn expanding_content(ui: &mut egui::Ui) {
|
||||||
char::from_u32(0x1f550 + row_index as u32 % 24)
|
let width = ui.available_width().clamp(20.0, 200.0);
|
||||||
.unwrap()
|
let height = ui.available_height();
|
||||||
.to_string()
|
let (rect, _response) = ui.allocate_exact_size(egui::vec2(width, height), egui::Sense::hover());
|
||||||
|
ui.painter().hline(
|
||||||
|
rect.x_range(),
|
||||||
|
rect.center().y,
|
||||||
|
(1.0, ui.visuals().text_color()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn long_text(row_index: usize) -> String {
|
||||||
|
format!("Row {row_index} has some long text that you may want to clip, or it will take up too much horizontal space!")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn thick_row(row_index: usize) -> bool {
|
fn thick_row(row_index: usize) -> bool {
|
||||||
|
|
|
@ -5,6 +5,21 @@ All notable changes to the `egui_extras` integration will be noted in this file.
|
||||||
## Unreleased
|
## Unreleased
|
||||||
* Added `TableBuilder::vertical_scroll_offset`: method to set vertical scroll offset position for a table ([#1946](https://github.com/emilk/egui/pull/1946)).
|
* Added `TableBuilder::vertical_scroll_offset`: method to set vertical scroll offset position for a table ([#1946](https://github.com/emilk/egui/pull/1946)).
|
||||||
* Added `RetainedImage::from_svg_bytes_with_size` to be able to specify a size for SVGs to be rasterized at.
|
* Added `RetainedImage::from_svg_bytes_with_size` to be able to specify a size for SVGs to be rasterized at.
|
||||||
|
* Big `Table` improvements ([#2369](https://github.com/emilk/egui/pull/2369)):
|
||||||
|
* Double-click column separators to auto-size the column.
|
||||||
|
* All `Table` now store state. You may see warnings about reused table ids. Use `ui.push_id` to fix this.
|
||||||
|
* `TableBuilder::column` takes a `Column` instead of a `Size`.
|
||||||
|
* `Column` controls default size, size range, resizing, and clipping of columns.
|
||||||
|
* `Column::auto` will pick a size automatically
|
||||||
|
* Added `Table::scroll_to_row`.
|
||||||
|
* Added `Table::min_scrolled_height` and `Table::max_scroll_height`.
|
||||||
|
* Added `TableBody::max_size`.
|
||||||
|
* `Table::scroll` renamed to `Table::vscroll`.
|
||||||
|
* `egui_extras::Strip` now has `clip: false` by default.
|
||||||
|
* Fix bugs when putting `Table` inside of a horizontal `ScrollArea`.
|
||||||
|
* Many other bug fixes.
|
||||||
|
* Add `Table::auto_shrink` - set to `false` to expand table to fit its containing `Ui` ([#2371](https://github.com/emilk/egui/pull/2371)).
|
||||||
|
|
||||||
|
|
||||||
## 0.19.0 - 2022-08-20
|
## 0.19.0 - 2022-08-20
|
||||||
* MSRV (Minimum Supported Rust Version) is now `1.61.0` ([#1846](https://github.com/emilk/egui/pull/1846)).
|
* MSRV (Minimum Supported Rust Version) is now `1.61.0` ([#1846](https://github.com/emilk/egui/pull/1846)).
|
||||||
|
|
|
@ -29,9 +29,6 @@ default = []
|
||||||
## Enable [`DatePickerButton`] widget.
|
## Enable [`DatePickerButton`] widget.
|
||||||
datepicker = ["chrono"]
|
datepicker = ["chrono"]
|
||||||
|
|
||||||
## Allow serialization using [`serde`](https://docs.rs/serde).
|
|
||||||
serde = ["dep:serde"]
|
|
||||||
|
|
||||||
## Support loading svg images.
|
## Support loading svg images.
|
||||||
svg = ["resvg", "tiny-skia", "usvg"]
|
svg = ["resvg", "tiny-skia", "usvg"]
|
||||||
|
|
||||||
|
@ -42,6 +39,8 @@ tracing = ["dep:tracing", "egui/tracing"]
|
||||||
[dependencies]
|
[dependencies]
|
||||||
egui = { version = "0.19.0", path = "../egui", default-features = false }
|
egui = { version = "0.19.0", path = "../egui", default-features = false }
|
||||||
|
|
||||||
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
|
||||||
#! ### Optional dependencies
|
#! ### Optional dependencies
|
||||||
|
|
||||||
# Date operations needed for datepicker widget
|
# Date operations needed for datepicker widget
|
||||||
|
@ -63,9 +62,6 @@ resvg = { version = "0.23", optional = true }
|
||||||
tiny-skia = { version = "0.6", optional = true } # must be updated in lock-step with resvg
|
tiny-skia = { version = "0.6", optional = true } # must be updated in lock-step with resvg
|
||||||
usvg = { version = "0.23", optional = true }
|
usvg = { version = "0.23", optional = true }
|
||||||
|
|
||||||
# feature "serde":
|
|
||||||
serde = { version = "1", features = ["derive"], optional = true }
|
|
||||||
|
|
||||||
# feature "tracing"
|
# feature "tracing"
|
||||||
tracing = { version = "0.1", optional = true, default-features = false, features = [
|
tracing = { version = "0.1", optional = true, default-features = false, features = [
|
||||||
"std",
|
"std",
|
||||||
|
|
|
@ -2,8 +2,7 @@ use super::popup::DatePickerPopup;
|
||||||
use chrono::{Date, Utc};
|
use chrono::{Date, Utc};
|
||||||
use egui::{Area, Button, Frame, InnerResponse, Key, Order, RichText, Ui, Widget};
|
use egui::{Area, Button, Frame, InnerResponse, Key, Order, RichText, Ui, Widget};
|
||||||
|
|
||||||
#[derive(Default, Clone)]
|
#[derive(Default, Clone, serde::Deserialize, serde::Serialize)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
|
||||||
pub(crate) struct DatePickerButtonState {
|
pub(crate) struct DatePickerButtonState {
|
||||||
pub picker_visible: bool,
|
pub picker_visible: bool,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
use super::{button::DatePickerButtonState, month_data};
|
|
||||||
use crate::{Size, StripBuilder, TableBuilder};
|
|
||||||
use chrono::{Date, Datelike, NaiveDate, Utc, Weekday};
|
use chrono::{Date, Datelike, NaiveDate, Utc, Weekday};
|
||||||
|
|
||||||
use egui::{Align, Button, Color32, ComboBox, Direction, Id, Layout, RichText, Ui, Vec2};
|
use egui::{Align, Button, Color32, ComboBox, Direction, Id, Layout, RichText, Ui, Vec2};
|
||||||
|
|
||||||
#[derive(Default, Clone)]
|
use super::{button::DatePickerButtonState, month_data};
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
|
||||||
|
use crate::{Column, Size, StripBuilder, TableBuilder};
|
||||||
|
|
||||||
|
#[derive(Default, Clone, serde::Deserialize, serde::Serialize)]
|
||||||
struct DatePickerPopupState {
|
struct DatePickerPopupState {
|
||||||
year: i32,
|
year: i32,
|
||||||
month: u32,
|
month: u32,
|
||||||
|
@ -243,9 +245,8 @@ impl<'a> DatePickerPopup<'a> {
|
||||||
strip.cell(|ui| {
|
strip.cell(|ui| {
|
||||||
ui.spacing_mut().item_spacing = Vec2::new(1.0, 2.0);
|
ui.spacing_mut().item_spacing = Vec2::new(1.0, 2.0);
|
||||||
TableBuilder::new(ui)
|
TableBuilder::new(ui)
|
||||||
.scroll(false)
|
.vscroll(false)
|
||||||
.clip(false)
|
.columns(Column::remainder(), if self.calendar_week { 8 } else { 7 })
|
||||||
.columns(Size::remainder(), if self.calendar_week { 8 } else { 7 })
|
|
||||||
.header(height, |mut header| {
|
.header(height, |mut header| {
|
||||||
if self.calendar_week {
|
if self.calendar_week {
|
||||||
header.col(|ui| {
|
header.col(|ui| {
|
||||||
|
|
|
@ -31,19 +31,15 @@ pub struct StripLayout<'l> {
|
||||||
pub(crate) ui: &'l mut Ui,
|
pub(crate) ui: &'l mut Ui,
|
||||||
direction: CellDirection,
|
direction: CellDirection,
|
||||||
pub(crate) rect: Rect,
|
pub(crate) rect: Rect,
|
||||||
cursor: Pos2,
|
pub(crate) cursor: Pos2,
|
||||||
|
/// Keeps track of the max used position,
|
||||||
|
/// so we know how much space we used.
|
||||||
max: Pos2,
|
max: Pos2,
|
||||||
pub(crate) clip: bool,
|
|
||||||
cell_layout: egui::Layout,
|
cell_layout: egui::Layout,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'l> StripLayout<'l> {
|
impl<'l> StripLayout<'l> {
|
||||||
pub(crate) fn new(
|
pub(crate) fn new(ui: &'l mut Ui, direction: CellDirection, cell_layout: egui::Layout) -> Self {
|
||||||
ui: &'l mut Ui,
|
|
||||||
direction: CellDirection,
|
|
||||||
clip: bool,
|
|
||||||
cell_layout: egui::Layout,
|
|
||||||
) -> Self {
|
|
||||||
let rect = ui.available_rect_before_wrap();
|
let rect = ui.available_rect_before_wrap();
|
||||||
let pos = rect.left_top();
|
let pos = rect.left_top();
|
||||||
|
|
||||||
|
@ -53,7 +49,6 @@ impl<'l> StripLayout<'l> {
|
||||||
rect,
|
rect,
|
||||||
cursor: pos,
|
cursor: pos,
|
||||||
max: pos,
|
max: pos,
|
||||||
clip,
|
|
||||||
cell_layout,
|
cell_layout,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -92,34 +87,41 @@ impl<'l> StripLayout<'l> {
|
||||||
self.set_pos(self.cell_rect(&width, &height));
|
self.set_pos(self.cell_rect(&width, &height));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This is the innermost part of [`crate::Table`] and [`crate::Strip`].
|
||||||
|
///
|
||||||
|
/// Return the used space (`min_rect`) plus the [`Response`] of the whole cell.
|
||||||
pub(crate) fn add(
|
pub(crate) fn add(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
clip: bool,
|
||||||
|
striped: bool,
|
||||||
width: CellSize,
|
width: CellSize,
|
||||||
height: CellSize,
|
height: CellSize,
|
||||||
add_contents: impl FnOnce(&mut Ui),
|
add_cell_contents: impl FnOnce(&mut Ui),
|
||||||
) -> Response {
|
) -> (Rect, Response) {
|
||||||
let rect = self.cell_rect(&width, &height);
|
let max_rect = self.cell_rect(&width, &height);
|
||||||
let used_rect = self.cell(rect, add_contents);
|
|
||||||
self.set_pos(rect);
|
|
||||||
self.ui.allocate_rect(rect.union(used_rect), Sense::hover())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn add_striped(
|
if striped {
|
||||||
&mut self,
|
// Make sure we don't have a gap in the stripe background:
|
||||||
width: CellSize,
|
let stripe_rect = max_rect.expand2(0.5 * self.ui.spacing().item_spacing);
|
||||||
height: CellSize,
|
|
||||||
add_contents: impl FnOnce(&mut Ui),
|
|
||||||
) -> Response {
|
|
||||||
let rect = self.cell_rect(&width, &height);
|
|
||||||
|
|
||||||
// Make sure we don't have a gap in the stripe background:
|
self.ui
|
||||||
let rect = rect.expand2(0.5 * self.ui.spacing().item_spacing);
|
.painter()
|
||||||
|
.rect_filled(stripe_rect, 0.0, self.ui.visuals().faint_bg_color);
|
||||||
|
}
|
||||||
|
|
||||||
self.ui
|
let used_rect = self.cell(clip, max_rect, add_cell_contents);
|
||||||
.painter()
|
|
||||||
.rect_filled(rect, 0.0, self.ui.visuals().faint_bg_color);
|
|
||||||
|
|
||||||
self.add(width, height, add_contents)
|
self.set_pos(max_rect);
|
||||||
|
|
||||||
|
let allocation_rect = if clip {
|
||||||
|
max_rect
|
||||||
|
} else {
|
||||||
|
max_rect.union(used_rect)
|
||||||
|
};
|
||||||
|
|
||||||
|
let response = self.ui.allocate_rect(allocation_rect, Sense::hover());
|
||||||
|
|
||||||
|
(used_rect, response)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// only needed for layouts with multiple lines, like [`Table`](crate::Table).
|
/// only needed for layouts with multiple lines, like [`Table`](crate::Table).
|
||||||
|
@ -144,17 +146,17 @@ impl<'l> StripLayout<'l> {
|
||||||
self.ui.allocate_rect(rect, Sense::hover());
|
self.ui.allocate_rect(rect, Sense::hover());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cell(&mut self, rect: Rect, add_contents: impl FnOnce(&mut Ui)) -> Rect {
|
fn cell(&mut self, clip: bool, rect: Rect, add_cell_contents: impl FnOnce(&mut Ui)) -> Rect {
|
||||||
let mut child_ui = self.ui.child_ui(rect, self.cell_layout);
|
let mut child_ui = self.ui.child_ui(rect, self.cell_layout);
|
||||||
|
|
||||||
if self.clip {
|
if clip {
|
||||||
let margin = egui::Vec2::splat(self.ui.visuals().clip_rect_margin);
|
let margin = egui::Vec2::splat(self.ui.visuals().clip_rect_margin);
|
||||||
let margin = margin.min(0.5 * self.ui.spacing().item_spacing);
|
let margin = margin.min(0.5 * self.ui.spacing().item_spacing);
|
||||||
let clip_rect = rect.expand2(margin);
|
let clip_rect = rect.expand2(margin);
|
||||||
child_ui.set_clip_rect(clip_rect.intersect(child_ui.clip_rect()));
|
child_ui.set_clip_rect(clip_rect.intersect(child_ui.clip_rect()));
|
||||||
}
|
}
|
||||||
|
|
||||||
add_contents(&mut child_ui);
|
add_cell_contents(&mut child_ui);
|
||||||
child_ui.min_rect()
|
child_ui.min_rect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -56,11 +56,11 @@ impl<'a> StripBuilder<'a> {
|
||||||
ui,
|
ui,
|
||||||
sizing: Default::default(),
|
sizing: Default::default(),
|
||||||
cell_layout,
|
cell_layout,
|
||||||
clip: true,
|
clip: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Should we clip the contents of each cell? Default: `true`.
|
/// Should we clip the contents of each cell? Default: `false`.
|
||||||
pub fn clip(mut self, clip: bool) -> Self {
|
pub fn clip(mut self, clip: bool) -> Self {
|
||||||
self.clip = clip;
|
self.clip = clip;
|
||||||
self
|
self
|
||||||
|
@ -98,15 +98,11 @@ impl<'a> StripBuilder<'a> {
|
||||||
self.ui.available_rect_before_wrap().width(),
|
self.ui.available_rect_before_wrap().width(),
|
||||||
self.ui.spacing().item_spacing.x,
|
self.ui.spacing().item_spacing.x,
|
||||||
);
|
);
|
||||||
let mut layout = StripLayout::new(
|
let mut layout = StripLayout::new(self.ui, CellDirection::Horizontal, self.cell_layout);
|
||||||
self.ui,
|
|
||||||
CellDirection::Horizontal,
|
|
||||||
self.clip,
|
|
||||||
self.cell_layout,
|
|
||||||
);
|
|
||||||
strip(Strip {
|
strip(Strip {
|
||||||
layout: &mut layout,
|
layout: &mut layout,
|
||||||
direction: CellDirection::Horizontal,
|
direction: CellDirection::Horizontal,
|
||||||
|
clip: self.clip,
|
||||||
sizes: widths,
|
sizes: widths,
|
||||||
size_index: 0,
|
size_index: 0,
|
||||||
});
|
});
|
||||||
|
@ -125,15 +121,11 @@ impl<'a> StripBuilder<'a> {
|
||||||
self.ui.available_rect_before_wrap().height(),
|
self.ui.available_rect_before_wrap().height(),
|
||||||
self.ui.spacing().item_spacing.y,
|
self.ui.spacing().item_spacing.y,
|
||||||
);
|
);
|
||||||
let mut layout = StripLayout::new(
|
let mut layout = StripLayout::new(self.ui, CellDirection::Vertical, self.cell_layout);
|
||||||
self.ui,
|
|
||||||
CellDirection::Vertical,
|
|
||||||
self.clip,
|
|
||||||
self.cell_layout,
|
|
||||||
);
|
|
||||||
strip(Strip {
|
strip(Strip {
|
||||||
layout: &mut layout,
|
layout: &mut layout,
|
||||||
direction: CellDirection::Vertical,
|
direction: CellDirection::Vertical,
|
||||||
|
clip: self.clip,
|
||||||
sizes: heights,
|
sizes: heights,
|
||||||
size_index: 0,
|
size_index: 0,
|
||||||
});
|
});
|
||||||
|
@ -146,6 +138,7 @@ impl<'a> StripBuilder<'a> {
|
||||||
pub struct Strip<'a, 'b> {
|
pub struct Strip<'a, 'b> {
|
||||||
layout: &'b mut StripLayout<'a>,
|
layout: &'b mut StripLayout<'a>,
|
||||||
direction: CellDirection,
|
direction: CellDirection,
|
||||||
|
clip: bool,
|
||||||
sizes: Vec<f32>,
|
sizes: Vec<f32>,
|
||||||
size_index: usize,
|
size_index: usize,
|
||||||
}
|
}
|
||||||
|
@ -172,7 +165,9 @@ impl<'a, 'b> Strip<'a, 'b> {
|
||||||
/// Add cell contents.
|
/// Add cell contents.
|
||||||
pub fn cell(&mut self, add_contents: impl FnOnce(&mut Ui)) {
|
pub fn cell(&mut self, add_contents: impl FnOnce(&mut Ui)) {
|
||||||
let (width, height) = self.next_cell_size();
|
let (width, height) = self.next_cell_size();
|
||||||
self.layout.add(width, height, add_contents);
|
let striped = false;
|
||||||
|
self.layout
|
||||||
|
.add(self.clip, striped, width, height, add_contents);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add an empty cell.
|
/// Add an empty cell.
|
||||||
|
@ -183,7 +178,7 @@ impl<'a, 'b> Strip<'a, 'b> {
|
||||||
|
|
||||||
/// Add a strip as cell.
|
/// Add a strip as cell.
|
||||||
pub fn strip(&mut self, strip_builder: impl FnOnce(StripBuilder<'_>)) {
|
pub fn strip(&mut self, strip_builder: impl FnOnce(StripBuilder<'_>)) {
|
||||||
let clip = self.layout.clip;
|
let clip = self.clip;
|
||||||
self.cell(|ui| {
|
self.cell(|ui| {
|
||||||
strip_builder(StripBuilder::new(ui).clip(clip));
|
strip_builder(StripBuilder::new(ui).clip(clip));
|
||||||
});
|
});
|
||||||
|
|
File diff suppressed because it is too large
Load diff
13
examples/keyboard_events/Cargo.toml
Normal file
13
examples/keyboard_events/Cargo.toml
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
[package]
|
||||||
|
name = "keyboard_events"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Jose Palazon <jose@palako.com>"]
|
||||||
|
license = "MIT OR Apache-2.0"
|
||||||
|
edition = "2021"
|
||||||
|
rust-version = "1.65"
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
eframe = { path = "../../crates/eframe" }
|
||||||
|
tracing-subscriber = "0.3"
|
3
examples/keyboard_events/README.md
Normal file
3
examples/keyboard_events/README.md
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
```sh
|
||||||
|
cargo run -p hello_world
|
||||||
|
```
|
48
examples/keyboard_events/src/main.rs
Normal file
48
examples/keyboard_events/src/main.rs
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
||||||
|
|
||||||
|
use eframe::egui;
|
||||||
|
use egui::*;
|
||||||
|
fn main() {
|
||||||
|
// Log to stdout (if you run with `RUST_LOG=debug`).
|
||||||
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
|
let options = eframe::NativeOptions::default();
|
||||||
|
eframe::run_native(
|
||||||
|
"Keyboard events",
|
||||||
|
options,
|
||||||
|
Box::new(|_cc| Box::new(Content::default())),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct Content {
|
||||||
|
text: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl eframe::App for Content {
|
||||||
|
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||||
|
egui::CentralPanel::default().show(ctx, |ui| {
|
||||||
|
ui.heading("Press/Hold/Release example. Press A to test.");
|
||||||
|
if ui.button("Clear").clicked() {
|
||||||
|
self.text.clear();
|
||||||
|
}
|
||||||
|
ScrollArea::vertical()
|
||||||
|
.auto_shrink([false; 2])
|
||||||
|
.stick_to_bottom(true)
|
||||||
|
.show(ui, |ui| {
|
||||||
|
ui.label(&self.text);
|
||||||
|
});
|
||||||
|
|
||||||
|
if ctx.input().key_pressed(Key::A) {
|
||||||
|
self.text.push_str("\nPressed");
|
||||||
|
}
|
||||||
|
if ctx.input().key_down(Key::A) {
|
||||||
|
self.text.push_str("\nHeld");
|
||||||
|
ui.ctx().request_repaint(); // make sure we note the holding.
|
||||||
|
}
|
||||||
|
if ctx.input().key_released(Key::A) {
|
||||||
|
self.text.push_str("\nReleased");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue