Horizontal scrolling (#663)
* First pass (missing rendering the bar) * Render horizontal bars, and change Window scroll API * emath: add impl Index + IndexMut for Align2 * Scrolling: fix subtle sizing bugs * Add horizontal scrolling to color test * try to wrap content before showing scrollbars, + add auto-shrink option * Add hscroll to the misc demo window * Fix for putting wrapping labels in an infinitely wide layout * Add a egui_asserts to protect against nans in the layout engine * Add line about horizontal scrolling to changelog * Add example to docs of ScrollArea * code cleanup
This commit is contained in:
parent
e98ae2ea7a
commit
105b999cb6
26 changed files with 464 additions and 194 deletions
|
@ -8,6 +8,10 @@ NOTE: [`eframe`](eframe/CHANGELOG.md), [`egui_web`](egui_web/CHANGELOG.md) and [
|
|||
## Unreleased
|
||||
|
||||
|
||||
### Added ⭐
|
||||
* Add horizontal scrolling support to `ScrollArea` and `Window` (opt-in).
|
||||
|
||||
|
||||
## 0.14.2 - 2021-08-28 - Window resize fix
|
||||
|
||||
### Fixed 🐛
|
||||
|
|
|
@ -188,7 +188,9 @@ fn combo_box<R>(
|
|||
ui.memory().toggle_popup(popup_id);
|
||||
}
|
||||
let inner = crate::popup::popup_below_widget(ui, popup_id, &button_response, |ui| {
|
||||
ScrollArea::from_max_height(ui.spacing().combo_height).show(ui, menu_contents)
|
||||
ScrollArea::vertical()
|
||||
.max_height(ui.spacing().combo_height)
|
||||
.show(ui, menu_contents)
|
||||
});
|
||||
|
||||
InnerResponse {
|
||||
|
|
|
@ -1,3 +1,10 @@
|
|||
//! Coordinate system names:
|
||||
//! * content: size of contents (generally large; that's why we want scroll bars)
|
||||
//! * outer: size of scroll area including scroll bar(s)
|
||||
//! * inner: excluding scroll bar(s). The area we clip the contents to.
|
||||
|
||||
#![allow(clippy::needless_range_loop)]
|
||||
|
||||
use crate::*;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
|
@ -7,48 +14,77 @@ pub(crate) struct State {
|
|||
/// Positive offset means scrolling down/right
|
||||
offset: Vec2,
|
||||
|
||||
show_scroll: bool,
|
||||
show_scroll: [bool; 2],
|
||||
|
||||
/// Momentum, used for kinetic scrolling
|
||||
#[cfg_attr(feature = "persistence", serde(skip))]
|
||||
pub vel: Vec2,
|
||||
|
||||
/// Mouse offset relative to the top of the handle when started moving the handle.
|
||||
scroll_start_offset_from_top: Option<f32>,
|
||||
scroll_start_offset_from_top_left: [Option<f32>; 2],
|
||||
}
|
||||
|
||||
impl Default for State {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
offset: Vec2::ZERO,
|
||||
show_scroll: false,
|
||||
show_scroll: [false; 2],
|
||||
vel: Vec2::ZERO,
|
||||
scroll_start_offset_from_top: None,
|
||||
scroll_start_offset_from_top_left: [None; 2],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: rename VScroll
|
||||
/// Add vertical scrolling to a contained [`Ui`].
|
||||
/// Add vertical and/or horizontal scrolling to a contained [`Ui`].
|
||||
///
|
||||
/// ```
|
||||
/// # let ui = &mut egui::Ui::__test();
|
||||
/// egui::ScrollArea::vertical().show(ui, |ui| {
|
||||
/// // Add a lot of widgets here.
|
||||
/// });
|
||||
#[derive(Clone, Debug)]
|
||||
#[must_use = "You should call .show()"]
|
||||
pub struct ScrollArea {
|
||||
max_height: f32,
|
||||
/// Do we have horizontal/vertical scrolling?
|
||||
has_bar: [bool; 2],
|
||||
auto_shrink: [bool; 2],
|
||||
max_size: Vec2,
|
||||
always_show_scroll: bool,
|
||||
id_source: Option<Id>,
|
||||
offset: Option<Vec2>,
|
||||
/// If false, we ignore scroll events.
|
||||
scrolling_enabled: bool,
|
||||
}
|
||||
|
||||
impl ScrollArea {
|
||||
/// Will make the area be as high as it is allowed to be (i.e. fill the [`Ui`] it is in)
|
||||
pub fn auto_sized() -> Self {
|
||||
Self::from_max_height(f32::INFINITY)
|
||||
/// Create a horizontal scroll area.
|
||||
pub fn horizontal() -> Self {
|
||||
Self::new([true, false])
|
||||
}
|
||||
|
||||
/// Use `f32::INFINITY` if you want the scroll area to expand to fit the surrounding Ui
|
||||
pub fn from_max_height(max_height: f32) -> Self {
|
||||
/// Create a vertical scroll area.
|
||||
pub fn vertical() -> Self {
|
||||
Self::new([false, true])
|
||||
}
|
||||
|
||||
/// Create a bi-directional (horizontal and vertical) scroll area.
|
||||
pub fn both() -> Self {
|
||||
Self::new([true, true])
|
||||
}
|
||||
|
||||
/// Create a scroll area where both direction of scrolling is disabled.
|
||||
/// It's unclear why you would want to do this.
|
||||
pub fn neither() -> Self {
|
||||
Self::new([false, false])
|
||||
}
|
||||
|
||||
/// Create a scroll area where you decide which axis has scrolling enabled.
|
||||
/// For instance, `ScrollAre::new([true, false])` enable horizontal scrolling.
|
||||
pub fn new(has_bar: [bool; 2]) -> Self {
|
||||
Self {
|
||||
max_height,
|
||||
has_bar,
|
||||
auto_shrink: [true; 2],
|
||||
max_size: Vec2::INFINITY,
|
||||
always_show_scroll: false,
|
||||
id_source: None,
|
||||
offset: None,
|
||||
|
@ -56,6 +92,34 @@ impl ScrollArea {
|
|||
}
|
||||
}
|
||||
|
||||
/// Will make the area be as high as it is allowed to be (i.e. fill the [`Ui`] it is in)
|
||||
#[deprecated = "Use pub ScrollArea::vertical() instead"]
|
||||
pub fn auto_sized() -> Self {
|
||||
Self::vertical()
|
||||
}
|
||||
|
||||
/// Use `f32::INFINITY` if you want the scroll area to expand to fit the surrounding Ui
|
||||
#[deprecated = "Use pub ScrollArea::vertical().max_height(…) instead"]
|
||||
pub fn from_max_height(max_height: f32) -> Self {
|
||||
Self::vertical().max_height(max_height)
|
||||
}
|
||||
|
||||
/// The desired width of the outer frame of the scroll area.
|
||||
///
|
||||
/// Use `f32::INFINITY` if you want the scroll area to expand to fit the surrounding `Ui` (default).
|
||||
pub fn max_width(mut self, max_width: f32) -> Self {
|
||||
self.max_size.x = max_width;
|
||||
self
|
||||
}
|
||||
|
||||
/// The desired height of the outer frame of the scroll area.
|
||||
///
|
||||
/// Use `f32::INFINITY` if you want the scroll area to expand to fit the surrounding `Ui` (default).
|
||||
pub fn max_height(mut self, max_height: f32) -> Self {
|
||||
self.max_size.y = max_height;
|
||||
self
|
||||
}
|
||||
|
||||
/// If `false` (default), the scroll bar will be hidden when not needed/
|
||||
/// If `true`, the scroll bar will always be displayed even if not needed.
|
||||
pub fn always_show_scroll(mut self, always_show_scroll: bool) -> Self {
|
||||
|
@ -78,22 +142,62 @@ impl ScrollArea {
|
|||
self
|
||||
}
|
||||
|
||||
/// Turn on/off scrolling on the horizontal axis.
|
||||
pub fn hscroll(mut self, hscroll: bool) -> Self {
|
||||
self.has_bar[0] = hscroll;
|
||||
self
|
||||
}
|
||||
|
||||
/// Turn on/off scrolling on the vertical axis.
|
||||
pub fn vscroll(mut self, vscroll: bool) -> Self {
|
||||
self.has_bar[1] = vscroll;
|
||||
self
|
||||
}
|
||||
|
||||
/// Turn on/off scrolling on the horizontal/vertical axes.
|
||||
pub fn scroll2(mut self, has_bar: [bool; 2]) -> Self {
|
||||
self.has_bar = has_bar;
|
||||
self
|
||||
}
|
||||
|
||||
/// Control the scrolling behavior
|
||||
/// If `true` (default), the scroll area will respond to user scrolling
|
||||
/// If `false`, the scroll area will not respond to user scrolling
|
||||
///
|
||||
/// This can be used, for example, to optionally freeze scrolling while the user
|
||||
/// is inputing text in a `TextEdit` widget contained within the scroll area
|
||||
/// is inputing text in a `TextEdit` widget contained within the scroll area.
|
||||
///
|
||||
/// This controls both scrolling directions.
|
||||
pub fn enable_scrolling(mut self, enable: bool) -> Self {
|
||||
self.scrolling_enabled = enable;
|
||||
self
|
||||
}
|
||||
|
||||
/// For each enabled axis, should the containing area shrink
|
||||
/// if the content is small?
|
||||
///
|
||||
/// If true, egui will add blank space outside the scroll area.
|
||||
/// If false, egui will add blank space inside the scroll area.
|
||||
///
|
||||
/// Default: `[true; 2]`.
|
||||
pub fn auto_shrink(mut self, auto_shrink: [bool; 2]) -> Self {
|
||||
self.auto_shrink = auto_shrink;
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn has_any_bar(&self) -> bool {
|
||||
self.has_bar[0] || self.has_bar[1]
|
||||
}
|
||||
}
|
||||
|
||||
struct Prepared {
|
||||
id: Id,
|
||||
state: State,
|
||||
current_scroll_bar_width: f32,
|
||||
has_bar: [bool; 2],
|
||||
auto_shrink: [bool; 2],
|
||||
/// How much horizontal and vertical space are used up by the
|
||||
/// width of the vertical bar, and the height of the horizontal bar?
|
||||
current_bar_use: Vec2,
|
||||
always_show_scroll: bool,
|
||||
inner_rect: Rect,
|
||||
content_ui: Ui,
|
||||
|
@ -106,7 +210,9 @@ struct Prepared {
|
|||
impl ScrollArea {
|
||||
fn begin(self, ui: &mut Ui) -> Prepared {
|
||||
let Self {
|
||||
max_height,
|
||||
has_bar,
|
||||
auto_shrink,
|
||||
max_size,
|
||||
always_show_scroll,
|
||||
id_source,
|
||||
offset,
|
||||
|
@ -123,38 +229,59 @@ impl ScrollArea {
|
|||
state.offset = offset;
|
||||
}
|
||||
|
||||
// content: size of contents (generally large; that's why we want scroll bars)
|
||||
// outer: size of scroll area including scroll bar(s)
|
||||
// inner: excluding scroll bar(s). The area we clip the contents to.
|
||||
|
||||
let max_scroll_bar_width = max_scroll_bar_width_with_margin(ui);
|
||||
|
||||
let current_scroll_bar_width = if always_show_scroll {
|
||||
let current_hscroll_bar_height = if !has_bar[0] {
|
||||
0.0
|
||||
} else if always_show_scroll {
|
||||
max_scroll_bar_width
|
||||
} else {
|
||||
max_scroll_bar_width * ui.ctx().animate_bool(id, state.show_scroll)
|
||||
max_scroll_bar_width * ui.ctx().animate_bool(id.with("h"), state.show_scroll[0])
|
||||
};
|
||||
|
||||
let current_vscroll_bar_width = if !has_bar[1] {
|
||||
0.0
|
||||
} else if always_show_scroll {
|
||||
max_scroll_bar_width
|
||||
} else {
|
||||
max_scroll_bar_width * ui.ctx().animate_bool(id.with("v"), state.show_scroll[1])
|
||||
};
|
||||
|
||||
let current_bar_use = vec2(current_vscroll_bar_width, current_hscroll_bar_height);
|
||||
|
||||
let available_outer = ui.available_rect_before_wrap();
|
||||
|
||||
let outer_size = vec2(
|
||||
available_outer.width(),
|
||||
available_outer.height().at_most(max_height),
|
||||
);
|
||||
let outer_size = available_outer.size().at_most(max_size);
|
||||
|
||||
let inner_size = outer_size - vec2(current_scroll_bar_width, 0.0);
|
||||
let inner_size = outer_size - current_bar_use;
|
||||
let inner_rect = Rect::from_min_size(available_outer.min, inner_size);
|
||||
|
||||
let mut inner_child_max_size = inner_size;
|
||||
|
||||
if true {
|
||||
// Tell the inner Ui to *try* to fit the content without needing to scroll,
|
||||
// i.e. better to wrap text than showing a horizontal scrollbar!
|
||||
} else {
|
||||
// Tell the inner Ui to use as much space as possible, we can scroll to see it!
|
||||
for d in 0..2 {
|
||||
if has_bar[d] {
|
||||
inner_child_max_size[d] = f32::INFINITY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut content_ui = ui.child_ui(
|
||||
Rect::from_min_size(
|
||||
inner_rect.min - state.offset,
|
||||
vec2(inner_size.x, f32::INFINITY),
|
||||
),
|
||||
Rect::from_min_size(inner_rect.min - state.offset, inner_child_max_size),
|
||||
*ui.layout(),
|
||||
);
|
||||
let mut content_clip_rect = inner_rect.expand(ui.visuals().clip_rect_margin);
|
||||
content_clip_rect = content_clip_rect.intersect(ui.clip_rect());
|
||||
content_clip_rect.max.x = ui.clip_rect().max.x - current_scroll_bar_width; // Nice handling of forced resizing beyond the possible
|
||||
// Nice handling of forced resizing beyond the possible:
|
||||
for d in 0..2 {
|
||||
if !has_bar[d] {
|
||||
content_clip_rect.max[d] = ui.clip_rect().max[d] - current_bar_use[d];
|
||||
}
|
||||
}
|
||||
content_ui.set_clip_rect(content_clip_rect);
|
||||
|
||||
let viewport = Rect::from_min_size(Pos2::ZERO + state.offset, inner_size);
|
||||
|
@ -162,7 +289,9 @@ impl ScrollArea {
|
|||
Prepared {
|
||||
id,
|
||||
state,
|
||||
current_scroll_bar_width,
|
||||
has_bar,
|
||||
auto_shrink,
|
||||
current_bar_use,
|
||||
always_show_scroll,
|
||||
inner_rect,
|
||||
content_ui,
|
||||
|
@ -186,7 +315,7 @@ impl ScrollArea {
|
|||
/// let row_height = ui.fonts()[text_style].row_height();
|
||||
/// // let row_height = ui.spacing().interact_size.y; // if you are adding buttons instead of labels.
|
||||
/// let num_rows = 10_000;
|
||||
/// egui::ScrollArea::auto_sized().show_rows(ui, row_height, num_rows, |ui, row_range| {
|
||||
/// egui::ScrollArea::vertical().show_rows(ui, row_height, num_rows, |ui, row_range| {
|
||||
/// for row in row_range {
|
||||
/// let text = format!("Row {}/{}", row + 1, num_rows);
|
||||
/// ui.label(text);
|
||||
|
@ -241,8 +370,10 @@ impl Prepared {
|
|||
id,
|
||||
mut state,
|
||||
inner_rect,
|
||||
has_bar,
|
||||
auto_shrink,
|
||||
mut current_bar_use,
|
||||
always_show_scroll,
|
||||
mut current_scroll_bar_width,
|
||||
content_ui,
|
||||
viewport: _,
|
||||
scrolling_enabled,
|
||||
|
@ -251,52 +382,67 @@ impl Prepared {
|
|||
let content_size = content_ui.min_size();
|
||||
|
||||
// We take the scroll target so only this ScrollArea will use it.
|
||||
let scroll_target = content_ui.ctx().frame_state().scroll_target.take();
|
||||
if let Some((scroll_y, align)) = scroll_target {
|
||||
let center_factor = align.to_factor();
|
||||
|
||||
let top = content_ui.min_rect().top();
|
||||
let visible_range = top..=top + content_ui.clip_rect().height();
|
||||
let offset_y = scroll_y - lerp(visible_range, center_factor);
|
||||
for d in 0..2 {
|
||||
if has_bar[d] {
|
||||
let scroll_target = content_ui.ctx().frame_state().scroll_target[d].take();
|
||||
if let Some((scroll, align)) = scroll_target {
|
||||
let center_factor = align.to_factor();
|
||||
|
||||
let mut spacing = ui.spacing().item_spacing.y;
|
||||
let min = content_ui.min_rect().min[d];
|
||||
let visible_range = min..=min + content_ui.clip_rect().size()[d];
|
||||
let offset = scroll - lerp(visible_range, center_factor);
|
||||
|
||||
// Depending on the alignment we need to add or subtract the spacing
|
||||
spacing *= remap(center_factor, 0.0..=1.0, -1.0..=1.0);
|
||||
let mut spacing = ui.spacing().item_spacing[d];
|
||||
|
||||
state.offset.y = offset_y + spacing;
|
||||
// Depending on the alignment we need to add or subtract the spacing
|
||||
spacing *= remap(center_factor, 0.0..=1.0, -1.0..=1.0);
|
||||
|
||||
state.offset[d] = offset + spacing;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let inner_rect = {
|
||||
let width = if inner_rect.width().is_finite() {
|
||||
inner_rect.width().max(content_size.x) // Expand width to fit content
|
||||
} else {
|
||||
// ScrollArea is in an infinitely wide parent
|
||||
content_size.x
|
||||
};
|
||||
let mut inner_size = inner_rect.size();
|
||||
|
||||
let mut inner_rect =
|
||||
Rect::from_min_size(inner_rect.min, vec2(width, inner_rect.height()));
|
||||
for d in 0..2 {
|
||||
inner_size[d] = if has_bar[d] {
|
||||
if auto_shrink[d] {
|
||||
inner_size[d].min(content_size[d]) // shrink scroll area if content is small
|
||||
} else {
|
||||
inner_size[d] // let scroll area be larger than content; fill with blank space
|
||||
}
|
||||
} else if inner_size[d].is_finite() {
|
||||
inner_size[d].max(content_size[d]) // Expand to fit content
|
||||
} else {
|
||||
content_size[d] // ScrollArea is in an infinitely sized parent; take size of parent
|
||||
};
|
||||
}
|
||||
|
||||
let mut inner_rect = 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:
|
||||
let max_x = ui.input().screen_rect().right()
|
||||
- current_scroll_bar_width
|
||||
- ui.spacing().item_spacing.x;
|
||||
let max_x =
|
||||
ui.input().screen_rect().right() - current_bar_use.x - ui.spacing().item_spacing.x;
|
||||
inner_rect.max.x = inner_rect.max.x.at_most(max_x);
|
||||
// TODO: when we support it, we should maybe auto-enable
|
||||
// horizontal scrolling if this limit is reached
|
||||
|
||||
let max_y =
|
||||
ui.input().screen_rect().bottom() - current_bar_use.y - ui.spacing().item_spacing.y;
|
||||
inner_rect.max.y = inner_rect.max.y.at_most(max_y);
|
||||
// TODO: 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() + vec2(current_scroll_bar_width, 0.0),
|
||||
);
|
||||
let outer_rect = Rect::from_min_size(inner_rect.min, inner_rect.size() + current_bar_use);
|
||||
|
||||
let content_is_too_small = content_size.y > inner_rect.height();
|
||||
let content_is_too_small = [
|
||||
content_size.x > inner_rect.width(),
|
||||
content_size.y > inner_rect.height(),
|
||||
];
|
||||
|
||||
if content_is_too_small {
|
||||
if content_is_too_small[0] || content_is_too_small[1] {
|
||||
// Drag contents to scroll (for touch screens mostly):
|
||||
let sense = if self.scrolling_enabled {
|
||||
Sense::drag()
|
||||
|
@ -307,8 +453,14 @@ impl Prepared {
|
|||
|
||||
let input = ui.input();
|
||||
if content_response.dragged() {
|
||||
state.offset.y -= input.pointer.delta().y;
|
||||
state.vel = input.pointer.velocity();
|
||||
for d in 0..2 {
|
||||
if has_bar[d] {
|
||||
state.offset[d] -= input.pointer.delta()[d];
|
||||
state.vel[d] = input.pointer.velocity()[d];
|
||||
} else {
|
||||
state.vel[d] = 0.0;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let stop_speed = 20.0; // Pixels per second.
|
||||
let friction_coeff = 1000.0; // Pixels per second squared.
|
||||
|
@ -321,59 +473,91 @@ impl Prepared {
|
|||
state.vel -= friction * state.vel.normalized();
|
||||
// Offset has an inverted coordinate system compared to
|
||||
// the velocity, so we subtract it instead of adding it
|
||||
state.offset.y -= state.vel.y * dt;
|
||||
state.offset -= state.vel * dt;
|
||||
ui.ctx().request_repaint();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let max_offset = content_size.y - inner_rect.height();
|
||||
let max_offset = content_size - inner_rect.size();
|
||||
if scrolling_enabled && ui.rect_contains_pointer(outer_rect) {
|
||||
let mut frame_state = ui.ctx().frame_state();
|
||||
let scroll_delta = frame_state.scroll_delta;
|
||||
for d in 0..2 {
|
||||
if has_bar[d] {
|
||||
let mut frame_state = ui.ctx().frame_state();
|
||||
let scroll_delta = frame_state.scroll_delta;
|
||||
|
||||
let scrolling_up = state.offset.y > 0.0 && scroll_delta.y > 0.0;
|
||||
let scrolling_down = state.offset.y < max_offset && scroll_delta.y < 0.0;
|
||||
let scrolling_up = state.offset[d] > 0.0 && scroll_delta[d] > 0.0;
|
||||
let scrolling_down = state.offset[d] < max_offset[d] && scroll_delta[d] < 0.0;
|
||||
|
||||
if scrolling_up || scrolling_down {
|
||||
state.offset.y -= scroll_delta.y;
|
||||
// Clear scroll delta so no parent scroll will use it.
|
||||
frame_state.scroll_delta = Vec2::ZERO;
|
||||
if scrolling_up || scrolling_down {
|
||||
state.offset[d] -= scroll_delta[d];
|
||||
// Clear scroll delta so no parent scroll will use it.
|
||||
frame_state.scroll_delta[d] = 0.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let show_scroll_this_frame = content_is_too_small || always_show_scroll;
|
||||
let show_scroll_this_frame = [
|
||||
content_is_too_small[0] || always_show_scroll,
|
||||
content_is_too_small[1] || always_show_scroll,
|
||||
];
|
||||
|
||||
let max_scroll_bar_width = max_scroll_bar_width_with_margin(ui);
|
||||
|
||||
if show_scroll_this_frame && current_scroll_bar_width <= 0.0 {
|
||||
// Avoid frame delay; start showing scroll bar right away:
|
||||
current_scroll_bar_width = max_scroll_bar_width * ui.ctx().animate_bool(id, true);
|
||||
// Avoid frame delay; start showing scroll bar right away:
|
||||
if show_scroll_this_frame[0] && current_bar_use.y <= 0.0 {
|
||||
current_bar_use.y = max_scroll_bar_width * ui.ctx().animate_bool(id.with("h"), true);
|
||||
}
|
||||
if show_scroll_this_frame[1] && current_bar_use.x <= 0.0 {
|
||||
current_bar_use.x = max_scroll_bar_width * ui.ctx().animate_bool(id.with("v"), true);
|
||||
}
|
||||
|
||||
if current_scroll_bar_width > 0.0 {
|
||||
let animation_t = current_scroll_bar_width / max_scroll_bar_width;
|
||||
for d in 0..2 {
|
||||
let animation_t = current_bar_use[1 - d] / max_scroll_bar_width;
|
||||
|
||||
if animation_t == 0.0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
// margin between contents and scroll bar
|
||||
let margin = animation_t * ui.spacing().item_spacing.x;
|
||||
let left = inner_rect.right() + margin;
|
||||
let right = outer_rect.right();
|
||||
let top = inner_rect.top();
|
||||
let bottom = inner_rect.bottom();
|
||||
let min_cross = inner_rect.max[1 - d] + margin; // left of vertical scroll (d == 1)
|
||||
let max_cross = outer_rect.max[1 - d]; // right 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 outer_scroll_rect = Rect::from_min_max(
|
||||
pos2(left, inner_rect.top()),
|
||||
pos2(right, inner_rect.bottom()),
|
||||
);
|
||||
let outer_scroll_rect = if d == 0 {
|
||||
Rect::from_min_max(
|
||||
pos2(inner_rect.left(), min_cross),
|
||||
pos2(inner_rect.right(), max_cross),
|
||||
)
|
||||
} else {
|
||||
Rect::from_min_max(
|
||||
pos2(min_cross, inner_rect.top()),
|
||||
pos2(max_cross, inner_rect.bottom()),
|
||||
)
|
||||
};
|
||||
|
||||
let from_content =
|
||||
|content_y| remap_clamp(content_y, 0.0..=content_size.y, top..=bottom);
|
||||
|content| remap_clamp(content, 0.0..=content_size[d], min_main..=max_main);
|
||||
|
||||
let handle_rect = Rect::from_min_max(
|
||||
pos2(left, from_content(state.offset.y)),
|
||||
pos2(right, from_content(state.offset.y + inner_rect.height())),
|
||||
);
|
||||
let handle_rect = if d == 0 {
|
||||
Rect::from_min_max(
|
||||
pos2(from_content(state.offset.x), min_cross),
|
||||
pos2(from_content(state.offset.x + inner_rect.width()), max_cross),
|
||||
)
|
||||
} else {
|
||||
Rect::from_min_max(
|
||||
pos2(min_cross, from_content(state.offset.y)),
|
||||
pos2(
|
||||
max_cross,
|
||||
from_content(state.offset.y + inner_rect.height()),
|
||||
),
|
||||
)
|
||||
};
|
||||
|
||||
let interact_id = id.with("vertical");
|
||||
let interact_id = id.with(d);
|
||||
let sense = if self.scrolling_enabled {
|
||||
Sense::click_and_drag()
|
||||
} else {
|
||||
|
@ -382,43 +566,57 @@ impl Prepared {
|
|||
let response = ui.interact(outer_scroll_rect, interact_id, sense);
|
||||
|
||||
if let Some(pointer_pos) = response.interact_pointer_pos() {
|
||||
let scroll_start_offset_from_top =
|
||||
state.scroll_start_offset_from_top.get_or_insert_with(|| {
|
||||
let scroll_start_offset_from_top_left = state.scroll_start_offset_from_top_left[d]
|
||||
.get_or_insert_with(|| {
|
||||
if handle_rect.contains(pointer_pos) {
|
||||
pointer_pos.y - handle_rect.top()
|
||||
pointer_pos[d] - handle_rect.min[d]
|
||||
} else {
|
||||
let handle_top_pos_at_bottom = bottom - handle_rect.height();
|
||||
let handle_top_pos_at_bottom = max_main - handle_rect.size()[d];
|
||||
// Calculate the new handle top position, centering the handle on the mouse.
|
||||
let new_handle_top_pos = (pointer_pos.y - handle_rect.height() / 2.0)
|
||||
.clamp(top, handle_top_pos_at_bottom);
|
||||
pointer_pos.y - new_handle_top_pos
|
||||
let new_handle_top_pos = (pointer_pos[d] - handle_rect.size()[d] / 2.0)
|
||||
.clamp(min_main, handle_top_pos_at_bottom);
|
||||
pointer_pos[d] - new_handle_top_pos
|
||||
}
|
||||
});
|
||||
|
||||
let new_handle_top = pointer_pos.y - *scroll_start_offset_from_top;
|
||||
state.offset.y = remap(new_handle_top, top..=bottom, 0.0..=content_size.y);
|
||||
let new_handle_top = pointer_pos[d] - *scroll_start_offset_from_top_left;
|
||||
state.offset[d] = remap(new_handle_top, min_main..=max_main, 0.0..=content_size[d]);
|
||||
} else {
|
||||
state.scroll_start_offset_from_top = None;
|
||||
state.scroll_start_offset_from_top_left[d] = None;
|
||||
}
|
||||
|
||||
let unbounded_offset_y = state.offset.y;
|
||||
state.offset.y = state.offset.y.max(0.0);
|
||||
state.offset.y = state.offset.y.min(max_offset);
|
||||
let unbounded_offset = state.offset[d];
|
||||
state.offset[d] = state.offset[d].max(0.0);
|
||||
state.offset[d] = state.offset[d].min(max_offset[d]);
|
||||
|
||||
if state.offset.y != unbounded_offset_y {
|
||||
state.vel = Vec2::ZERO;
|
||||
if state.offset[d] != unbounded_offset {
|
||||
state.vel[d] = 0.0;
|
||||
}
|
||||
|
||||
// Avoid frame-delay by calculating a new handle rect:
|
||||
let mut handle_rect = Rect::from_min_max(
|
||||
pos2(left, from_content(state.offset.y)),
|
||||
pos2(right, from_content(state.offset.y + inner_rect.height())),
|
||||
);
|
||||
let min_handle_height = ui.spacing().scroll_bar_width;
|
||||
if handle_rect.size().y < min_handle_height {
|
||||
let mut handle_rect = if d == 0 {
|
||||
Rect::from_min_max(
|
||||
pos2(from_content(state.offset.x), min_cross),
|
||||
pos2(from_content(state.offset.x + inner_rect.width()), max_cross),
|
||||
)
|
||||
} else {
|
||||
Rect::from_min_max(
|
||||
pos2(min_cross, from_content(state.offset.y)),
|
||||
pos2(
|
||||
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 {
|
||||
handle_rect = Rect::from_center_size(
|
||||
handle_rect.center(),
|
||||
vec2(handle_rect.size().x, min_handle_height),
|
||||
if d == 0 {
|
||||
vec2(min_handle_size, handle_rect.size().y)
|
||||
} else {
|
||||
vec2(handle_rect.size().x, min_handle_size)
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -441,24 +639,21 @@ impl Prepared {
|
|||
));
|
||||
}
|
||||
|
||||
let size = vec2(
|
||||
outer_rect.size().x,
|
||||
outer_rect.size().y.min(content_size.y), // shrink if content is so small that we don't need scroll bars
|
||||
);
|
||||
ui.advance_cursor_after_rect(Rect::from_min_size(outer_rect.min, size));
|
||||
ui.advance_cursor_after_rect(outer_rect);
|
||||
|
||||
if show_scroll_this_frame != state.show_scroll {
|
||||
ui.ctx().request_repaint();
|
||||
}
|
||||
|
||||
state.offset.y = state.offset.y.min(content_size.y - inner_rect.height());
|
||||
state.offset.y = state.offset.y.max(0.0);
|
||||
state.offset = state.offset.min(content_size - inner_rect.size());
|
||||
state.offset = state.offset.max(Vec2::ZERO);
|
||||
state.show_scroll = show_scroll_this_frame;
|
||||
|
||||
ui.memory().id_data.insert(id, state);
|
||||
}
|
||||
}
|
||||
|
||||
/// Width of a vertical scrollbar, or height of a horizontal scroll bar
|
||||
fn max_scroll_bar_width_with_margin(ui: &Ui) -> f32 {
|
||||
ui.spacing().item_spacing.x + ui.spacing().scroll_bar_width
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ pub struct Window<'open> {
|
|||
area: Area,
|
||||
frame: Option<Frame>,
|
||||
resize: Resize,
|
||||
scroll: Option<ScrollArea>,
|
||||
scroll: ScrollArea,
|
||||
collapsible: bool,
|
||||
with_title_bar: bool,
|
||||
}
|
||||
|
@ -51,7 +51,7 @@ impl<'open> Window<'open> {
|
|||
.with_stroke(false)
|
||||
.min_size([96.0, 32.0])
|
||||
.default_size([340.0, 420.0]), // Default inner size of a window
|
||||
scroll: None,
|
||||
scroll: ScrollArea::neither(),
|
||||
collapsible: true,
|
||||
with_title_bar: true,
|
||||
}
|
||||
|
@ -203,26 +203,33 @@ impl<'open> Window<'open> {
|
|||
/// Text will not wrap, but will instead make your window width expand.
|
||||
pub fn auto_sized(mut self) -> Self {
|
||||
self.resize = self.resize.auto_sized();
|
||||
self.scroll = None;
|
||||
self.scroll = ScrollArea::neither();
|
||||
self
|
||||
}
|
||||
|
||||
/// Enable/disable scrolling. `false` by default.
|
||||
pub fn scroll(mut self, scroll: bool) -> Self {
|
||||
if scroll {
|
||||
if self.scroll.is_none() {
|
||||
self.scroll = Some(ScrollArea::auto_sized());
|
||||
}
|
||||
crate::egui_assert!(
|
||||
self.scroll.is_some(),
|
||||
"Window::scroll called multiple times"
|
||||
);
|
||||
} else {
|
||||
self.scroll = None;
|
||||
}
|
||||
/// Enable/disable horizontal/vertical scrolling. `false` by default.
|
||||
pub fn scroll2(mut self, scroll: [bool; 2]) -> Self {
|
||||
self.scroll = self.scroll.scroll2(scroll);
|
||||
self
|
||||
}
|
||||
|
||||
/// Enable/disable horizontal scrolling. `false` by default.
|
||||
pub fn hscroll(mut self, hscroll: bool) -> Self {
|
||||
self.scroll = self.scroll.hscroll(hscroll);
|
||||
self
|
||||
}
|
||||
|
||||
/// Enable/disable vertical scrolling. `false` by default.
|
||||
pub fn vscroll(mut self, vscroll: bool) -> Self {
|
||||
self.scroll = self.scroll.vscroll(vscroll);
|
||||
self
|
||||
}
|
||||
|
||||
#[deprecated = "Use .vscroll(…) instead"]
|
||||
pub fn scroll(self, scroll: bool) -> Self {
|
||||
self.vscroll(scroll)
|
||||
}
|
||||
|
||||
/// Constrain the area up to which the window can be dragged.
|
||||
pub fn drag_bounds(mut self, bounds: Rect) -> Self {
|
||||
self.area = self.area.drag_bounds(bounds);
|
||||
|
@ -352,7 +359,7 @@ impl<'open> Window<'open> {
|
|||
ui.add_space(title_content_spacing);
|
||||
}
|
||||
|
||||
if let Some(scroll) = scroll {
|
||||
if scroll.has_any_bar() {
|
||||
scroll.show(ui, add_contents)
|
||||
} else {
|
||||
add_contents(ui)
|
||||
|
|
|
@ -27,8 +27,9 @@ pub(crate) struct FrameState {
|
|||
pub(crate) tooltip_rect: Option<(Id, Rect, usize)>,
|
||||
|
||||
/// Cleared by the first `ScrollArea` that makes use of it.
|
||||
pub(crate) scroll_delta: Vec2,
|
||||
pub(crate) scroll_target: Option<(f32, Align)>,
|
||||
pub(crate) scroll_delta: Vec2, // TODO: move to a Mutex inside of `InputState` ?
|
||||
/// horizontal, vertical
|
||||
pub(crate) scroll_target: [Option<(f32, Align)>; 2],
|
||||
}
|
||||
|
||||
impl Default for FrameState {
|
||||
|
@ -40,7 +41,7 @@ impl Default for FrameState {
|
|||
used_by_panels: Rect::NAN,
|
||||
tooltip_rect: None,
|
||||
scroll_delta: Vec2::ZERO,
|
||||
scroll_target: None,
|
||||
scroll_target: [None; 2],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -63,7 +64,7 @@ impl FrameState {
|
|||
*used_by_panels = Rect::NOTHING;
|
||||
*tooltip_rect = None;
|
||||
*scroll_delta = input.scroll_delta;
|
||||
*scroll_target = None;
|
||||
*scroll_target = [None; 2];
|
||||
}
|
||||
|
||||
/// How much space is still available after panels has been added.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::{emath::*, Align};
|
||||
use crate::{egui_assert, emath::*, Align};
|
||||
use std::f32::INFINITY;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
@ -84,6 +84,12 @@ impl Region {
|
|||
self.max_rect.extend_with_y(y);
|
||||
self.cursor.extend_with_y(y);
|
||||
}
|
||||
|
||||
pub fn sanity_check(&self) {
|
||||
egui_assert!(!self.min_rect.any_nan());
|
||||
egui_assert!(!self.max_rect.any_nan());
|
||||
egui_assert!(!self.cursor.any_nan());
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
@ -398,6 +404,9 @@ impl Layout {
|
|||
/// Given the cursor in the region, how much space is available
|
||||
/// for the next widget?
|
||||
fn available_from_cursor_max_rect(&self, cursor: Rect, max_rect: Rect) -> Rect {
|
||||
egui_assert!(!cursor.any_nan());
|
||||
egui_assert!(!max_rect.any_nan());
|
||||
|
||||
// NOTE: in normal top-down layout the cursor has moved below the current max_rect,
|
||||
// but the available shouldn't be negative.
|
||||
|
||||
|
@ -450,6 +459,8 @@ impl Layout {
|
|||
avail.max.y = y;
|
||||
}
|
||||
|
||||
egui_assert!(!avail.any_nan());
|
||||
|
||||
avail
|
||||
}
|
||||
|
||||
|
@ -458,6 +469,7 @@ impl Layout {
|
|||
/// This is what you then pass to `advance_after_rects`.
|
||||
/// Use `justify_and_align` to get the inner `widget_rect`.
|
||||
pub(crate) fn next_frame(&self, region: &Region, child_size: Vec2, spacing: Vec2) -> Rect {
|
||||
region.sanity_check();
|
||||
crate::egui_assert!(child_size.x >= 0.0 && child_size.y >= 0.0);
|
||||
|
||||
if self.main_wrap {
|
||||
|
@ -537,6 +549,7 @@ impl Layout {
|
|||
}
|
||||
|
||||
fn next_frame_ignore_wrap(&self, region: &Region, child_size: Vec2) -> Rect {
|
||||
region.sanity_check();
|
||||
crate::egui_assert!(child_size.x >= 0.0 && child_size.y >= 0.0);
|
||||
|
||||
let available_rect = self.available_rect_before_wrap_finite(region);
|
||||
|
@ -570,6 +583,9 @@ impl Layout {
|
|||
frame_rect = frame_rect.translate(Vec2::Y * (region.cursor.top() - frame_rect.top()));
|
||||
}
|
||||
|
||||
egui_assert!(!frame_rect.any_nan());
|
||||
egui_assert!(!frame_rect.is_negative());
|
||||
|
||||
frame_rect
|
||||
}
|
||||
|
||||
|
@ -595,6 +611,7 @@ impl Layout {
|
|||
let frame = self.next_frame_ignore_wrap(region, size);
|
||||
let rect = self.align_size_within_rect(size, frame);
|
||||
crate::egui_assert!(!rect.any_nan());
|
||||
crate::egui_assert!(!rect.is_negative());
|
||||
crate::egui_assert!((rect.width() - size.x).abs() < 1.0 || size.x == f32::INFINITY);
|
||||
crate::egui_assert!((rect.height() - size.y).abs() < 1.0 || size.y == f32::INFINITY);
|
||||
rect
|
||||
|
@ -639,6 +656,7 @@ impl Layout {
|
|||
widget_rect: Rect,
|
||||
item_spacing: Vec2,
|
||||
) {
|
||||
egui_assert!(!cursor.any_nan());
|
||||
if self.main_wrap {
|
||||
if cursor.intersects(frame_rect.shrink(1.0)) {
|
||||
// make row/column larger if necessary
|
||||
|
|
|
@ -119,6 +119,7 @@ impl Placer {
|
|||
/// This is what you then pass to `advance_after_rects`.
|
||||
/// Use `justify_and_align` to get the inner `widget_rect`.
|
||||
pub(crate) fn next_space(&self, child_size: Vec2, item_spacing: Vec2) -> Rect {
|
||||
self.region.sanity_check();
|
||||
if let Some(grid) = &self.grid {
|
||||
grid.next_cell(self.region.cursor, child_size)
|
||||
} else {
|
||||
|
@ -169,6 +170,10 @@ impl Placer {
|
|||
widget_rect: Rect,
|
||||
item_spacing: Vec2,
|
||||
) {
|
||||
egui_assert!(!frame_rect.any_nan());
|
||||
egui_assert!(!widget_rect.any_nan());
|
||||
self.region.sanity_check();
|
||||
|
||||
if let Some(grid) = &mut self.grid {
|
||||
grid.advance(&mut self.region.cursor, frame_rect, widget_rect)
|
||||
} else {
|
||||
|
@ -181,6 +186,8 @@ impl Placer {
|
|||
}
|
||||
|
||||
self.expand_to_include_rect(frame_rect); // e.g. for centered layouts: pretend we used whole frame
|
||||
|
||||
self.region.sanity_check();
|
||||
}
|
||||
|
||||
/// Move to the next row in a grid layout or wrapping layout.
|
||||
|
@ -231,6 +238,8 @@ impl Placer {
|
|||
|
||||
region.cursor.min.x = region.max_rect.min.x;
|
||||
region.cursor.max.x = region.max_rect.max.x;
|
||||
|
||||
region.sanity_check();
|
||||
}
|
||||
|
||||
/// Set the maximum height of the ui.
|
||||
|
@ -244,6 +253,8 @@ impl Placer {
|
|||
|
||||
region.cursor.min.y = region.max_rect.min.y;
|
||||
region.cursor.max.y = region.max_rect.max.y;
|
||||
|
||||
region.sanity_check();
|
||||
}
|
||||
|
||||
/// Set the minimum width of the ui.
|
||||
|
|
|
@ -422,7 +422,7 @@ impl Response {
|
|||
/// ```
|
||||
/// # use egui::Align;
|
||||
/// # let mut ui = &mut egui::Ui::__test();
|
||||
/// egui::ScrollArea::auto_sized().show(ui, |ui| {
|
||||
/// egui::ScrollArea::vertical().show(ui, |ui| {
|
||||
/// for i in 0..1000 {
|
||||
/// let response = ui.button(format!("Button {}", i));
|
||||
/// if response.clicked() {
|
||||
|
@ -432,8 +432,11 @@ impl Response {
|
|||
/// });
|
||||
/// ```
|
||||
pub fn scroll_to_me(&self, align: Align) {
|
||||
let scroll_target = lerp(self.rect.x_range(), align.to_factor());
|
||||
self.ctx.frame_state().scroll_target[0] = Some((scroll_target, align));
|
||||
|
||||
let scroll_target = lerp(self.rect.y_range(), align.to_factor());
|
||||
self.ctx.frame_state().scroll_target = Some((scroll_target, align));
|
||||
self.ctx.frame_state().scroll_target[1] = Some((scroll_target, align));
|
||||
}
|
||||
|
||||
/// For accessibility.
|
||||
|
|
|
@ -696,6 +696,7 @@ impl Ui {
|
|||
fn allocate_space_impl(&mut self, desired_size: Vec2) -> Rect {
|
||||
let item_spacing = self.spacing().item_spacing;
|
||||
let frame_rect = self.placer.next_space(desired_size, item_spacing);
|
||||
egui_assert!(!frame_rect.any_nan());
|
||||
let widget_rect = self.placer.justify_and_align(frame_rect, desired_size);
|
||||
|
||||
self.placer
|
||||
|
@ -714,6 +715,7 @@ impl Ui {
|
|||
}
|
||||
|
||||
pub(crate) fn advance_cursor_after_rect(&mut self, rect: Rect) -> Id {
|
||||
egui_assert!(!rect.any_nan());
|
||||
let item_spacing = self.spacing().item_spacing;
|
||||
self.placer.advance_after_rects(rect, rect, item_spacing);
|
||||
|
||||
|
@ -856,7 +858,7 @@ impl Ui {
|
|||
/// ```
|
||||
/// # use egui::Align;
|
||||
/// # let mut ui = &mut egui::Ui::__test();
|
||||
/// egui::ScrollArea::auto_sized().show(ui, |ui| {
|
||||
/// egui::ScrollArea::vertical().show(ui, |ui| {
|
||||
/// let scroll_bottom = ui.button("Scroll to bottom.").clicked();
|
||||
/// for i in 0..1000 {
|
||||
/// ui.label(format!("Item {}", i));
|
||||
|
@ -868,8 +870,10 @@ impl Ui {
|
|||
/// });
|
||||
/// ```
|
||||
pub fn scroll_to_cursor(&mut self, align: Align) {
|
||||
let target_y = self.next_widget_position().y;
|
||||
self.ctx().frame_state().scroll_target = Some((target_y, align));
|
||||
let target = self.next_widget_position();
|
||||
for d in 0..2 {
|
||||
self.ctx().frame_state().scroll_target[d] = Some((target[d], align));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -314,16 +314,19 @@ impl Widget for Label {
|
|||
fn ui(self, ui: &mut Ui) -> Response {
|
||||
let sense = self.sense;
|
||||
|
||||
let max_width = ui.available_width();
|
||||
|
||||
if self.should_wrap(ui)
|
||||
&& ui.layout().main_dir() == Direction::LeftToRight
|
||||
&& ui.layout().main_wrap()
|
||||
&& max_width.is_finite()
|
||||
{
|
||||
// On a wrapping horizontal layout we want text to start after the previous widget,
|
||||
// then continue on the line below! This will take some extra work:
|
||||
|
||||
let cursor = ui.cursor();
|
||||
let max_width = ui.available_width();
|
||||
let first_row_indentation = max_width - ui.available_size_before_wrap().x;
|
||||
egui_assert!(first_row_indentation.is_finite());
|
||||
|
||||
let text_style = self.text_style_or_default(ui.style());
|
||||
let galley = ui.fonts().layout_multiline_with_indentation_and_max_width(
|
||||
|
|
|
@ -42,7 +42,7 @@ impl epi::App for ColorTest {
|
|||
);
|
||||
ui.separator();
|
||||
}
|
||||
ScrollArea::auto_sized().show(ui, |ui| {
|
||||
ScrollArea::both().auto_shrink([false; 2]).show(ui, |ui| {
|
||||
self.ui(ui, &mut Some(frame.tex_allocator()));
|
||||
});
|
||||
});
|
||||
|
@ -55,6 +55,8 @@ impl ColorTest {
|
|||
ui: &mut Ui,
|
||||
mut tex_allocator: &mut Option<&mut dyn epi::TextureAllocator>,
|
||||
) {
|
||||
ui.set_max_width(680.0);
|
||||
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.add(crate::__egui_github_link_file!());
|
||||
});
|
||||
|
|
|
@ -15,7 +15,7 @@ impl super::Demo for DancingStrings {
|
|||
Window::new(self.name())
|
||||
.open(open)
|
||||
.default_size(vec2(512.0, 256.0))
|
||||
.scroll(false)
|
||||
.vscroll(false)
|
||||
.show(ctx, |ui| self.ui(ui));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -159,7 +159,7 @@ impl DemoWindows {
|
|||
|
||||
ui.separator();
|
||||
|
||||
ScrollArea::auto_sized().show(ui, |ui| {
|
||||
ScrollArea::vertical().show(ui, |ui| {
|
||||
use egui::special_emojis::{GITHUB, OS_APPLE, OS_LINUX, OS_WINDOWS};
|
||||
|
||||
ui.label("egui is an immediate mode GUI library written in Rust.");
|
||||
|
|
|
@ -103,7 +103,7 @@ impl super::Demo for DragAndDropDemo {
|
|||
Window::new(self.name())
|
||||
.open(open)
|
||||
.default_size(vec2(256.0, 256.0))
|
||||
.scroll(false)
|
||||
.vscroll(false)
|
||||
.resizable(false)
|
||||
.show(ctx, |ui| self.ui(ui));
|
||||
}
|
||||
|
|
|
@ -84,7 +84,7 @@ impl super::View for FontBook {
|
|||
|
||||
ui.separator();
|
||||
|
||||
egui::ScrollArea::auto_sized().show(ui, |ui| {
|
||||
egui::ScrollArea::vertical().show(ui, |ui| {
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
ui.spacing_mut().item_spacing = egui::Vec2::splat(2.0);
|
||||
|
||||
|
|
|
@ -34,13 +34,16 @@ impl Demo for MiscDemoWindow {
|
|||
fn show(&mut self, ctx: &CtxRef, open: &mut bool) {
|
||||
Window::new(self.name())
|
||||
.open(open)
|
||||
.scroll(true)
|
||||
.vscroll(true)
|
||||
.hscroll(true)
|
||||
.show(ctx, |ui| self.ui(ui));
|
||||
}
|
||||
}
|
||||
|
||||
impl View for MiscDemoWindow {
|
||||
fn ui(&mut self, ui: &mut Ui) {
|
||||
ui.set_min_width(250.0);
|
||||
|
||||
CollapsingHeader::new("Widgets")
|
||||
.default_open(true)
|
||||
.show(ui, |ui| {
|
||||
|
|
|
@ -79,7 +79,7 @@ impl super::Demo for Painting {
|
|||
Window::new(self.name())
|
||||
.open(open)
|
||||
.default_size(vec2(512.0, 512.0))
|
||||
.scroll(false)
|
||||
.vscroll(false)
|
||||
.show(ctx, |ui| self.ui(ui));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -401,7 +401,7 @@ impl super::Demo for PlotDemo {
|
|||
Window::new(self.name())
|
||||
.open(open)
|
||||
.default_size(vec2(400.0, 400.0))
|
||||
.scroll(false)
|
||||
.vscroll(false)
|
||||
.show(ctx, |ui| self.ui(ui));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -77,7 +77,7 @@ fn huge_content_lines(ui: &mut egui::Ui) {
|
|||
let text_style = TextStyle::Body;
|
||||
let row_height = ui.fonts()[text_style].row_height();
|
||||
let num_rows = 10_000;
|
||||
ScrollArea::auto_sized().show_rows(ui, row_height, num_rows, |ui, row_range| {
|
||||
ScrollArea::vertical().show_rows(ui, row_height, num_rows, |ui, row_range| {
|
||||
for row in row_range {
|
||||
let text = format!("This is row {}/{}", row + 1, num_rows);
|
||||
ui.label(text);
|
||||
|
@ -94,7 +94,7 @@ fn huge_content_painter(ui: &mut egui::Ui) {
|
|||
let row_height = ui.fonts()[text_style].row_height() + ui.spacing().item_spacing.y;
|
||||
let num_rows = 10_000;
|
||||
|
||||
ScrollArea::auto_sized().show_viewport(ui, |ui, viewport| {
|
||||
ScrollArea::vertical().show_viewport(ui, |ui, viewport| {
|
||||
ui.set_height(row_height * num_rows as f32);
|
||||
|
||||
let first_item = (viewport.min.y / row_height).floor().at_least(0.0) as usize;
|
||||
|
@ -184,7 +184,7 @@ impl super::View for ScrollTo {
|
|||
scroll_bottom |= ui.button("Scroll to bottom").clicked();
|
||||
});
|
||||
|
||||
let mut scroll_area = ScrollArea::from_max_height(200.0);
|
||||
let mut scroll_area = ScrollArea::vertical().max_height(200.0);
|
||||
if go_to_scroll_offset {
|
||||
scroll_area = scroll_area.scroll_offset(self.offset);
|
||||
}
|
||||
|
|
|
@ -400,7 +400,7 @@ impl super::Demo for WindowResizeTest {
|
|||
|
||||
Window::new("↔ resizable + scroll")
|
||||
.open(open)
|
||||
.scroll(true)
|
||||
.vscroll(true)
|
||||
.resizable(true)
|
||||
.default_height(300.0)
|
||||
.show(ctx, |ui| {
|
||||
|
@ -413,14 +413,14 @@ impl super::Demo for WindowResizeTest {
|
|||
|
||||
Window::new("↔ resizable + embedded scroll")
|
||||
.open(open)
|
||||
.scroll(false)
|
||||
.vscroll(false)
|
||||
.resizable(true)
|
||||
.default_height(300.0)
|
||||
.show(ctx, |ui| {
|
||||
ui.label("This window is resizable but has no built-in scroll area.");
|
||||
ui.label("However, we have a sub-region with a scroll bar:");
|
||||
ui.separator();
|
||||
ScrollArea::auto_sized().show(ui, |ui| {
|
||||
ScrollArea::vertical().show(ui, |ui| {
|
||||
ui.code(crate::LOREM_IPSUM_LONG);
|
||||
ui.code(crate::LOREM_IPSUM_LONG);
|
||||
});
|
||||
|
@ -429,7 +429,7 @@ impl super::Demo for WindowResizeTest {
|
|||
|
||||
Window::new("↔ resizable without scroll")
|
||||
.open(open)
|
||||
.scroll(false)
|
||||
.vscroll(false)
|
||||
.resizable(true)
|
||||
.show(ctx, |ui| {
|
||||
ui.label("This window is resizable but has no scroll area. This means it can only be resized to a size where all the contents is visible.");
|
||||
|
@ -440,7 +440,7 @@ impl super::Demo for WindowResizeTest {
|
|||
|
||||
Window::new("↔ resizable with TextEdit")
|
||||
.open(open)
|
||||
.scroll(false)
|
||||
.vscroll(false)
|
||||
.resizable(true)
|
||||
.default_height(300.0)
|
||||
.show(ctx, |ui| {
|
||||
|
@ -450,7 +450,7 @@ impl super::Demo for WindowResizeTest {
|
|||
|
||||
Window::new("↔ freely resized")
|
||||
.open(open)
|
||||
.scroll(false)
|
||||
.vscroll(false)
|
||||
.resizable(true)
|
||||
.default_size([250.0, 150.0])
|
||||
.show(ctx, |ui| {
|
||||
|
|
|
@ -6,7 +6,7 @@ pub struct WindowOptions {
|
|||
closable: bool,
|
||||
collapsible: bool,
|
||||
resizable: bool,
|
||||
scroll: bool,
|
||||
scroll2: [bool; 2],
|
||||
disabled_time: f64,
|
||||
|
||||
anchored: bool,
|
||||
|
@ -21,8 +21,8 @@ impl Default for WindowOptions {
|
|||
title_bar: true,
|
||||
closable: true,
|
||||
collapsible: true,
|
||||
resizable: false,
|
||||
scroll: false,
|
||||
resizable: true,
|
||||
scroll2: [true; 2],
|
||||
disabled_time: f64::NEG_INFINITY,
|
||||
anchored: false,
|
||||
anchor: egui::Align2::RIGHT_TOP,
|
||||
|
@ -43,7 +43,7 @@ impl super::Demo for WindowOptions {
|
|||
closable,
|
||||
collapsible,
|
||||
resizable,
|
||||
scroll,
|
||||
scroll2,
|
||||
disabled_time,
|
||||
anchored,
|
||||
anchor,
|
||||
|
@ -61,7 +61,7 @@ impl super::Demo for WindowOptions {
|
|||
.resizable(resizable)
|
||||
.collapsible(collapsible)
|
||||
.title_bar(title_bar)
|
||||
.scroll(scroll)
|
||||
.scroll2(scroll2)
|
||||
.enabled(enabled);
|
||||
if closable {
|
||||
window = window.open(open);
|
||||
|
@ -81,7 +81,7 @@ impl super::View for WindowOptions {
|
|||
closable,
|
||||
collapsible,
|
||||
resizable,
|
||||
scroll,
|
||||
scroll2,
|
||||
disabled_time: _,
|
||||
anchored,
|
||||
anchor,
|
||||
|
@ -100,7 +100,8 @@ impl super::View for WindowOptions {
|
|||
ui.checkbox(closable, "closable");
|
||||
ui.checkbox(collapsible, "collapsible");
|
||||
ui.checkbox(resizable, "resizable");
|
||||
ui.checkbox(scroll, "scroll");
|
||||
ui.checkbox(&mut scroll2[0], "hscroll");
|
||||
ui.checkbox(&mut scroll2[1], "vscroll");
|
||||
});
|
||||
});
|
||||
ui.group(|ui| {
|
||||
|
@ -109,15 +110,15 @@ impl super::View for WindowOptions {
|
|||
ui.set_enabled(*anchored);
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("x:");
|
||||
ui.selectable_value(&mut anchor.0[0], egui::Align::LEFT, "Left");
|
||||
ui.selectable_value(&mut anchor.0[0], egui::Align::Center, "Center");
|
||||
ui.selectable_value(&mut anchor.0[0], egui::Align::RIGHT, "Right");
|
||||
ui.selectable_value(&mut anchor[0], egui::Align::LEFT, "Left");
|
||||
ui.selectable_value(&mut anchor[0], egui::Align::Center, "Center");
|
||||
ui.selectable_value(&mut anchor[0], egui::Align::RIGHT, "Right");
|
||||
});
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("y:");
|
||||
ui.selectable_value(&mut anchor.0[1], egui::Align::TOP, "Top");
|
||||
ui.selectable_value(&mut anchor.0[1], egui::Align::Center, "Center");
|
||||
ui.selectable_value(&mut anchor.0[1], egui::Align::BOTTOM, "Bottom");
|
||||
ui.selectable_value(&mut anchor[1], egui::Align::TOP, "Top");
|
||||
ui.selectable_value(&mut anchor[1], egui::Align::Center, "Center");
|
||||
ui.selectable_value(&mut anchor[1], egui::Align::BOTTOM, "Bottom");
|
||||
});
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Offset:");
|
||||
|
|
|
@ -12,7 +12,7 @@ impl super::Demo for WindowWithPanels {
|
|||
let window = egui::Window::new("Window with Panels")
|
||||
.default_width(600.0)
|
||||
.default_height(400.0)
|
||||
.scroll(false)
|
||||
.vscroll(false)
|
||||
.open(open);
|
||||
window.show(ctx, |ui| self.ui(ui));
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ impl super::View for WindowWithPanels {
|
|||
.resizable(true)
|
||||
.min_height(32.0)
|
||||
.show_inside(ui, |ui| {
|
||||
egui::ScrollArea::auto_sized().show(ui, |ui| {
|
||||
egui::ScrollArea::vertical().show(ui, |ui| {
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.heading("Expandable Upper Panel");
|
||||
});
|
||||
|
@ -42,7 +42,7 @@ impl super::View for WindowWithPanels {
|
|||
ui.vertical_centered(|ui| {
|
||||
ui.heading("Left Panel");
|
||||
});
|
||||
egui::ScrollArea::auto_sized().show(ui, |ui| {
|
||||
egui::ScrollArea::vertical().show(ui, |ui| {
|
||||
ui.add(egui::Label::new(crate::LOREM_IPSUM_LONG).small().weak());
|
||||
});
|
||||
});
|
||||
|
@ -55,7 +55,7 @@ impl super::View for WindowWithPanels {
|
|||
ui.vertical_centered(|ui| {
|
||||
ui.heading("Right Panel");
|
||||
});
|
||||
egui::ScrollArea::auto_sized().show(ui, |ui| {
|
||||
egui::ScrollArea::vertical().show(ui, |ui| {
|
||||
ui.add(egui::Label::new(crate::LOREM_IPSUM_LONG).small().weak());
|
||||
});
|
||||
});
|
||||
|
@ -73,7 +73,7 @@ impl super::View for WindowWithPanels {
|
|||
ui.vertical_centered(|ui| {
|
||||
ui.heading("Central Panel");
|
||||
});
|
||||
egui::ScrollArea::auto_sized().show(ui, |ui| {
|
||||
egui::ScrollArea::vertical().show(ui, |ui| {
|
||||
ui.add(egui::Label::new(crate::LOREM_IPSUM_LONG).small().weak());
|
||||
});
|
||||
});
|
||||
|
|
|
@ -264,7 +264,7 @@ fn ui_resource(
|
|||
|
||||
ui.separator();
|
||||
|
||||
egui::ScrollArea::auto_sized().show(ui, |ui| {
|
||||
egui::ScrollArea::vertical().show(ui, |ui| {
|
||||
if let Some(image) = image {
|
||||
if let Some(texture_id) = tex_mngr.texture(frame, &response.url, image) {
|
||||
let size = egui::Vec2::new(image.size.0 as f32, image.size.1 as f32);
|
||||
|
|
|
@ -323,14 +323,14 @@ impl EguiWindows {
|
|||
|
||||
egui::Window::new("🔧 Settings")
|
||||
.open(settings)
|
||||
.scroll(true)
|
||||
.vscroll(true)
|
||||
.show(ctx, |ui| {
|
||||
ctx.settings_ui(ui);
|
||||
});
|
||||
|
||||
egui::Window::new("🔍 Inspection")
|
||||
.open(inspection)
|
||||
.scroll(true)
|
||||
.vscroll(true)
|
||||
.show(ctx, |ui| {
|
||||
ctx.inspection_ui(ui);
|
||||
});
|
||||
|
|
|
@ -34,14 +34,14 @@ impl EasyMarkEditor {
|
|||
});
|
||||
ui.separator();
|
||||
ui.columns(2, |columns| {
|
||||
ScrollArea::auto_sized()
|
||||
ScrollArea::vertical()
|
||||
.id_source("source")
|
||||
.show(&mut columns[0], |ui| {
|
||||
ui.add(TextEdit::multiline(&mut self.code).text_style(TextStyle::Monospace));
|
||||
// let cursor = TextEdit::cursor(response.id);
|
||||
// TODO: cmd-i, cmd-b, etc for italics, bold, ....
|
||||
});
|
||||
ScrollArea::auto_sized()
|
||||
ScrollArea::vertical()
|
||||
.id_source("rendered")
|
||||
.show(&mut columns[1], |ui| {
|
||||
crate::easy_mark::easy_mark(ui, &self.code);
|
||||
|
|
|
@ -172,6 +172,22 @@ impl Align2 {
|
|||
}
|
||||
}
|
||||
|
||||
impl std::ops::Index<usize> for Align2 {
|
||||
type Output = Align;
|
||||
|
||||
#[inline(always)]
|
||||
fn index(&self, index: usize) -> &Align {
|
||||
&self.0[index]
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::IndexMut<usize> for Align2 {
|
||||
#[inline(always)]
|
||||
fn index_mut(&mut self, index: usize) -> &mut Align {
|
||||
&mut self.0[index]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn center_size_in_rect(size: Vec2, frame: Rect) -> Rect {
|
||||
Align2::CENTER_CENTER.align_size_within_rect(size, frame)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue