Floating scroll bars (#3539)
* Move scroll bar spacing settings to a `struct ScrollSpacing` * Add a demo for changing scroll bar appearance * Add setting for ScrollBarVisibility in demo * Add `#[inline]` to a `ScrollArea` builder methods * Refactor how scroll bar show/hide is computed * Add support for floating scroll bars * Tweak color and opacity of the scroll handle * Allow allocating a fixed size even for floating scroll bars * Add three pre-sets of scroll bars: solid, thin, floating * Use floating scroll bars as the default * Fix id-clash with bidir scroll areas * Improve demo * Fix doclink * Remove reset button from demo * Fix doclinks * Fix visual artifact with thin rounded rectangles * Fix doclink * typos
This commit is contained in:
parent
f218825d94
commit
41f9df5cb3
|
|
@ -25,7 +25,7 @@ struct ValueAnim {
|
|||
}
|
||||
|
||||
impl AnimationManager {
|
||||
/// See `Context::animate_bool` for documentation
|
||||
/// See [`crate::Context::animate_bool`] for documentation
|
||||
pub fn animate_bool(
|
||||
&mut self,
|
||||
input: &InputState,
|
||||
|
|
|
|||
|
|
@ -1,8 +1,3 @@
|
|||
//! 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::*;
|
||||
|
|
@ -20,6 +15,9 @@ pub struct State {
|
|||
/// The content were to large to fit large frame.
|
||||
content_is_too_large: [bool; 2],
|
||||
|
||||
/// Did the user interact (hover or drag) the scroll bars last frame?
|
||||
scroll_bar_interaction: [bool; 2],
|
||||
|
||||
/// Momentum, used for kinetic scrolling
|
||||
#[cfg_attr(feature = "serde", serde(skip))]
|
||||
vel: Vec2,
|
||||
|
|
@ -39,6 +37,7 @@ impl Default for State {
|
|||
offset: Vec2::ZERO,
|
||||
show_scroll: [false; 2],
|
||||
content_is_too_large: [false; 2],
|
||||
scroll_bar_interaction: [false; 2],
|
||||
vel: Vec2::ZERO,
|
||||
scroll_start_offset_from_top_left: [None; 2],
|
||||
scroll_stuck_to_end: [true; 2],
|
||||
|
|
@ -80,15 +79,61 @@ pub struct ScrollAreaOutput<R> {
|
|||
}
|
||||
|
||||
/// Indicate whether the horizontal and vertical scroll bars must be always visible, hidden or visible when needed.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub enum ScrollBarVisibility {
|
||||
AlwaysVisible,
|
||||
VisibleWhenNeeded,
|
||||
/// Hide scroll bar even if they are needed.
|
||||
///
|
||||
/// You can still scroll, with the scroll-wheel
|
||||
/// and by dragging the contents, but there is no
|
||||
/// visual indication of how far you have scrolled.
|
||||
AlwaysHidden,
|
||||
|
||||
/// Show scroll bars only when the content size exceeds the container,
|
||||
/// i.e. when there is any need to scroll.
|
||||
///
|
||||
/// This is the default.
|
||||
VisibleWhenNeeded,
|
||||
|
||||
/// Always show the scroll bar, even if the contents fit in the container
|
||||
/// and there is no need to scroll.
|
||||
AlwaysVisible,
|
||||
}
|
||||
|
||||
impl Default for ScrollBarVisibility {
|
||||
#[inline]
|
||||
fn default() -> Self {
|
||||
Self::VisibleWhenNeeded
|
||||
}
|
||||
}
|
||||
|
||||
impl ScrollBarVisibility {
|
||||
pub const ALL: [Self; 3] = [
|
||||
Self::AlwaysHidden,
|
||||
Self::VisibleWhenNeeded,
|
||||
Self::AlwaysVisible,
|
||||
];
|
||||
}
|
||||
|
||||
/// Add vertical and/or horizontal scrolling to a contained [`Ui`].
|
||||
///
|
||||
/// By default, scroll bars only show up when needed, i.e. when the contents
|
||||
/// is larger than the container.
|
||||
/// This is controlled by [`Self::scroll_bar_visibility`].
|
||||
///
|
||||
/// There are two flavors of scroll areas: solid and floating.
|
||||
/// Solid scroll bars use up space, reducing the amount of space available
|
||||
/// to the contents. Floating scroll bars float on top of the contents, covering it.
|
||||
/// You can change the scroll style by changing the [`crate::style::Spacing::scroll`].
|
||||
///
|
||||
/// ### Coordinate system
|
||||
/// * 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.
|
||||
///
|
||||
/// If the floating scroll bars settings is turned on then `inner == outer`.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```
|
||||
/// # egui::__run_test_ui(|ui| {
|
||||
/// egui::ScrollArea::vertical().show(ui, |ui| {
|
||||
|
|
@ -101,8 +146,9 @@ pub enum ScrollBarVisibility {
|
|||
#[derive(Clone, Debug)]
|
||||
#[must_use = "You should call .show()"]
|
||||
pub struct ScrollArea {
|
||||
/// Do we have horizontal/vertical scrolling?
|
||||
has_bar: [bool; 2],
|
||||
/// Do we have horizontal/vertical scrolling enabled?
|
||||
scroll_enabled: [bool; 2],
|
||||
|
||||
auto_shrink: [bool; 2],
|
||||
max_size: Vec2,
|
||||
min_scrolled_size: Vec2,
|
||||
|
|
@ -123,35 +169,39 @@ pub struct ScrollArea {
|
|||
|
||||
impl ScrollArea {
|
||||
/// Create a horizontal scroll area.
|
||||
#[inline]
|
||||
pub fn horizontal() -> Self {
|
||||
Self::new([true, false])
|
||||
}
|
||||
|
||||
/// Create a vertical scroll area.
|
||||
#[inline]
|
||||
pub fn vertical() -> Self {
|
||||
Self::new([false, true])
|
||||
}
|
||||
|
||||
/// Create a bi-directional (horizontal and vertical) scroll area.
|
||||
#[inline]
|
||||
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.
|
||||
#[inline]
|
||||
pub fn neither() -> Self {
|
||||
Self::new([false, false])
|
||||
}
|
||||
|
||||
/// Create a scroll area where you decide which axis has scrolling enabled.
|
||||
/// For instance, `ScrollArea::new([true, false])` enables horizontal scrolling.
|
||||
pub fn new(has_bar: [bool; 2]) -> Self {
|
||||
pub fn new(scroll_enabled: [bool; 2]) -> Self {
|
||||
Self {
|
||||
has_bar,
|
||||
scroll_enabled,
|
||||
auto_shrink: [true; 2],
|
||||
max_size: Vec2::INFINITY,
|
||||
min_scrolled_size: Vec2::splat(64.0),
|
||||
scroll_bar_visibility: ScrollBarVisibility::VisibleWhenNeeded,
|
||||
scroll_bar_visibility: Default::default(),
|
||||
id_source: None,
|
||||
offset_x: None,
|
||||
offset_y: None,
|
||||
|
|
@ -166,6 +216,7 @@ impl ScrollArea {
|
|||
/// Use `f32::INFINITY` if you want the scroll area to expand to fit the surrounding [`Ui`] (default).
|
||||
///
|
||||
/// See also [`Self::auto_shrink`].
|
||||
#[inline]
|
||||
pub fn max_width(mut self, max_width: f32) -> Self {
|
||||
self.max_size.x = max_width;
|
||||
self
|
||||
|
|
@ -176,6 +227,7 @@ impl ScrollArea {
|
|||
/// Use `f32::INFINITY` if you want the scroll area to expand to fit the surrounding [`Ui`] (default).
|
||||
///
|
||||
/// See also [`Self::auto_shrink`].
|
||||
#[inline]
|
||||
pub fn max_height(mut self, max_height: f32) -> Self {
|
||||
self.max_size.y = max_height;
|
||||
self
|
||||
|
|
@ -187,6 +239,7 @@ impl ScrollArea {
|
|||
/// (and so we don't require scroll bars).
|
||||
///
|
||||
/// Default: `64.0`.
|
||||
#[inline]
|
||||
pub fn min_scrolled_width(mut self, min_scrolled_width: f32) -> Self {
|
||||
self.min_scrolled_size.x = min_scrolled_width;
|
||||
self
|
||||
|
|
@ -198,6 +251,7 @@ impl ScrollArea {
|
|||
/// (and so we don't require scroll bars).
|
||||
///
|
||||
/// Default: `64.0`.
|
||||
#[inline]
|
||||
pub fn min_scrolled_height(mut self, min_scrolled_height: f32) -> Self {
|
||||
self.min_scrolled_size.y = min_scrolled_height;
|
||||
self
|
||||
|
|
@ -206,12 +260,14 @@ impl ScrollArea {
|
|||
/// Set the visibility of both horizontal and vertical scroll bars.
|
||||
///
|
||||
/// With `ScrollBarVisibility::VisibleWhenNeeded` (default), the scroll bar will be visible only when needed.
|
||||
#[inline]
|
||||
pub fn scroll_bar_visibility(mut self, scroll_bar_visibility: ScrollBarVisibility) -> Self {
|
||||
self.scroll_bar_visibility = scroll_bar_visibility;
|
||||
self
|
||||
}
|
||||
|
||||
/// A source for the unique [`Id`], e.g. `.id_source("second_scroll_area")` or `.id_source(loop_index)`.
|
||||
#[inline]
|
||||
pub fn id_source(mut self, id_source: impl std::hash::Hash) -> Self {
|
||||
self.id_source = Some(Id::new(id_source));
|
||||
self
|
||||
|
|
@ -224,6 +280,7 @@ impl ScrollArea {
|
|||
/// See also: [`Self::vertical_scroll_offset`], [`Self::horizontal_scroll_offset`],
|
||||
/// [`Ui::scroll_to_cursor`](crate::ui::Ui::scroll_to_cursor) and
|
||||
/// [`Response::scroll_to_me`](crate::Response::scroll_to_me)
|
||||
#[inline]
|
||||
pub fn scroll_offset(mut self, offset: Vec2) -> Self {
|
||||
self.offset_x = Some(offset.x);
|
||||
self.offset_y = Some(offset.y);
|
||||
|
|
@ -236,6 +293,7 @@ impl ScrollArea {
|
|||
///
|
||||
/// 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)
|
||||
#[inline]
|
||||
pub fn vertical_scroll_offset(mut self, offset: f32) -> Self {
|
||||
self.offset_y = Some(offset);
|
||||
self
|
||||
|
|
@ -247,26 +305,30 @@ impl ScrollArea {
|
|||
///
|
||||
/// 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)
|
||||
#[inline]
|
||||
pub fn horizontal_scroll_offset(mut self, offset: f32) -> Self {
|
||||
self.offset_x = Some(offset);
|
||||
self
|
||||
}
|
||||
|
||||
/// Turn on/off scrolling on the horizontal axis.
|
||||
#[inline]
|
||||
pub fn hscroll(mut self, hscroll: bool) -> Self {
|
||||
self.has_bar[0] = hscroll;
|
||||
self.scroll_enabled[0] = hscroll;
|
||||
self
|
||||
}
|
||||
|
||||
/// Turn on/off scrolling on the vertical axis.
|
||||
#[inline]
|
||||
pub fn vscroll(mut self, vscroll: bool) -> Self {
|
||||
self.has_bar[1] = vscroll;
|
||||
self.scroll_enabled[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;
|
||||
#[inline]
|
||||
pub fn scroll2(mut self, scroll_enabled: [bool; 2]) -> Self {
|
||||
self.scroll_enabled = scroll_enabled;
|
||||
self
|
||||
}
|
||||
|
||||
|
|
@ -279,6 +341,7 @@ impl ScrollArea {
|
|||
/// is typing text in a [`TextEdit`] widget contained within the scroll area.
|
||||
///
|
||||
/// This controls both scrolling directions.
|
||||
#[inline]
|
||||
pub fn enable_scrolling(mut self, enable: bool) -> Self {
|
||||
self.scrolling_enabled = enable;
|
||||
self
|
||||
|
|
@ -291,6 +354,7 @@ impl ScrollArea {
|
|||
/// If `true`, the [`ScrollArea`] will sense drags.
|
||||
///
|
||||
/// Default: `true`.
|
||||
#[inline]
|
||||
pub fn drag_to_scroll(mut self, drag_to_scroll: bool) -> Self {
|
||||
self.drag_to_scroll = drag_to_scroll;
|
||||
self
|
||||
|
|
@ -302,13 +366,15 @@ impl ScrollArea {
|
|||
/// * If `false`, egui will add blank space inside the scroll area.
|
||||
///
|
||||
/// Default: `[true; 2]`.
|
||||
#[inline]
|
||||
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]
|
||||
/// Is any scrolling enabled?
|
||||
pub(crate) fn is_any_scroll_enabled(&self) -> bool {
|
||||
self.scroll_enabled[0] || self.scroll_enabled[1]
|
||||
}
|
||||
|
||||
/// The scroll handle will stick to the rightmost position even while the content size
|
||||
|
|
@ -317,6 +383,7 @@ impl ScrollArea {
|
|||
/// it will remain focused on whatever content viewport the user left it on. If the scroll
|
||||
/// handle is dragged all the way to the right it will again become stuck and remain there
|
||||
/// until manually pulled from the end position.
|
||||
#[inline]
|
||||
pub fn stick_to_right(mut self, stick: bool) -> Self {
|
||||
self.stick_to_end[0] = stick;
|
||||
self
|
||||
|
|
@ -328,6 +395,7 @@ impl ScrollArea {
|
|||
/// it will remain focused on whatever content viewport the user left it on. If the scroll
|
||||
/// handle is dragged to the bottom it will again become stuck and remain there until manually
|
||||
/// pulled from the end position.
|
||||
#[inline]
|
||||
pub fn stick_to_bottom(mut self, stick: bool) -> Self {
|
||||
self.stick_to_end[1] = stick;
|
||||
self
|
||||
|
|
@ -337,11 +405,24 @@ impl ScrollArea {
|
|||
struct Prepared {
|
||||
id: Id,
|
||||
state: State,
|
||||
has_bar: [bool; 2],
|
||||
|
||||
auto_shrink: [bool; 2],
|
||||
|
||||
/// Does this `ScrollArea` have horizontal/vertical scrolling enabled?
|
||||
scroll_enabled: [bool; 2],
|
||||
|
||||
/// Smoothly interpolated boolean of whether or not to show the scroll bars.
|
||||
show_bars_factor: Vec2,
|
||||
|
||||
/// How much horizontal and vertical space are used up by the
|
||||
/// width of the vertical bar, and the height of the horizontal bar?
|
||||
///
|
||||
/// This is always zero for floating scroll bars.
|
||||
///
|
||||
/// Note that this is a `yx` swizzling of [`Self::show_bars_factor`]
|
||||
/// times the maximum bar with.
|
||||
/// That's because horizontal scroll uses up vertical space,
|
||||
/// and vice versa.
|
||||
current_bar_use: Vec2,
|
||||
|
||||
scroll_bar_visibility: ScrollBarVisibility,
|
||||
|
|
@ -362,7 +443,7 @@ struct Prepared {
|
|||
impl ScrollArea {
|
||||
fn begin(self, ui: &mut Ui) -> Prepared {
|
||||
let Self {
|
||||
has_bar,
|
||||
scroll_enabled,
|
||||
auto_shrink,
|
||||
max_size,
|
||||
min_scrolled_size,
|
||||
|
|
@ -379,7 +460,7 @@ impl ScrollArea {
|
|||
|
||||
let id_source = id_source.unwrap_or_else(|| Id::new("scroll_area"));
|
||||
let id = ui.make_persistent_id(id_source);
|
||||
ui.ctx().check_for_id_clash(
|
||||
ctx.check_for_id_clash(
|
||||
id,
|
||||
Rect::from_min_size(ui.available_rect_before_wrap().min, Vec2::ZERO),
|
||||
"ScrollArea",
|
||||
|
|
@ -389,25 +470,18 @@ impl ScrollArea {
|
|||
state.offset.x = offset_x.unwrap_or(state.offset.x);
|
||||
state.offset.y = offset_y.unwrap_or(state.offset.y);
|
||||
|
||||
let max_scroll_bar_width = max_scroll_bar_width_with_margin(ui);
|
||||
|
||||
let current_hscroll_bar_height = if !has_bar[0] {
|
||||
0.0
|
||||
} else if scroll_bar_visibility == ScrollBarVisibility::AlwaysVisible {
|
||||
max_scroll_bar_width
|
||||
} else {
|
||||
max_scroll_bar_width * ui.ctx().animate_bool(id.with("h"), state.show_scroll[0])
|
||||
let show_bars: [bool; 2] = match scroll_bar_visibility {
|
||||
ScrollBarVisibility::AlwaysHidden => [false; 2],
|
||||
ScrollBarVisibility::VisibleWhenNeeded => state.show_scroll,
|
||||
ScrollBarVisibility::AlwaysVisible => scroll_enabled,
|
||||
};
|
||||
|
||||
let current_vscroll_bar_width = if !has_bar[1] {
|
||||
0.0
|
||||
} else if scroll_bar_visibility == ScrollBarVisibility::AlwaysVisible {
|
||||
max_scroll_bar_width
|
||||
} else {
|
||||
max_scroll_bar_width * ui.ctx().animate_bool(id.with("v"), state.show_scroll[1])
|
||||
};
|
||||
let show_bars_factor = Vec2::new(
|
||||
ctx.animate_bool(id.with("h"), show_bars[0]),
|
||||
ctx.animate_bool(id.with("v"), show_bars[1]),
|
||||
);
|
||||
|
||||
let current_bar_use = vec2(current_vscroll_bar_width, current_hscroll_bar_height);
|
||||
let current_bar_use = show_bars_factor.yx() * ui.spacing().scroll.allocated_width();
|
||||
|
||||
let available_outer = ui.available_rect_before_wrap();
|
||||
|
||||
|
|
@ -421,7 +495,7 @@ impl ScrollArea {
|
|||
// one shouldn't collapse into nothingness.
|
||||
// See https://github.com/emilk/egui/issues/1097
|
||||
for d in 0..2 {
|
||||
if has_bar[d] {
|
||||
if scroll_enabled[d] {
|
||||
inner_size[d] = inner_size[d].max(min_scrolled_size[d]);
|
||||
}
|
||||
}
|
||||
|
|
@ -438,7 +512,7 @@ impl ScrollArea {
|
|||
} 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] {
|
||||
if scroll_enabled[d] {
|
||||
content_max_size[d] = f32::INFINITY;
|
||||
}
|
||||
}
|
||||
|
|
@ -452,7 +526,7 @@ impl ScrollArea {
|
|||
let clip_rect_margin = ui.visuals().clip_rect_margin;
|
||||
let mut content_clip_rect = ui.clip_rect();
|
||||
for d in 0..2 {
|
||||
if has_bar[d] {
|
||||
if scroll_enabled[d] {
|
||||
if state.content_is_too_large[d] {
|
||||
content_clip_rect.min[d] = inner_rect.min[d] - clip_rect_margin;
|
||||
content_clip_rect.max[d] = inner_rect.max[d] + clip_rect_margin;
|
||||
|
|
@ -479,7 +553,7 @@ impl ScrollArea {
|
|||
|
||||
if content_response.dragged() {
|
||||
for d in 0..2 {
|
||||
if has_bar[d] {
|
||||
if scroll_enabled[d] {
|
||||
ui.input(|input| {
|
||||
state.offset[d] -= input.pointer.delta()[d];
|
||||
state.vel[d] = input.pointer.velocity()[d];
|
||||
|
|
@ -502,7 +576,7 @@ impl ScrollArea {
|
|||
// Offset has an inverted coordinate system compared to
|
||||
// the velocity, so we subtract it instead of adding it
|
||||
state.offset -= state.vel * dt;
|
||||
ui.ctx().request_repaint();
|
||||
ctx.request_repaint();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -510,8 +584,9 @@ impl ScrollArea {
|
|||
Prepared {
|
||||
id,
|
||||
state,
|
||||
has_bar,
|
||||
auto_shrink,
|
||||
scroll_enabled,
|
||||
show_bars_factor,
|
||||
current_bar_use,
|
||||
scroll_bar_visibility,
|
||||
inner_rect,
|
||||
|
|
@ -621,9 +696,10 @@ impl Prepared {
|
|||
id,
|
||||
mut state,
|
||||
inner_rect,
|
||||
has_bar,
|
||||
auto_shrink,
|
||||
mut current_bar_use,
|
||||
scroll_enabled,
|
||||
mut show_bars_factor,
|
||||
current_bar_use,
|
||||
scroll_bar_visibility,
|
||||
content_ui,
|
||||
viewport: _,
|
||||
|
|
@ -634,7 +710,7 @@ impl Prepared {
|
|||
let content_size = content_ui.min_size();
|
||||
|
||||
for d in 0..2 {
|
||||
if has_bar[d] {
|
||||
if scroll_enabled[d] {
|
||||
// We take the scroll target so only this ScrollArea will use it:
|
||||
let scroll_target = content_ui
|
||||
.ctx()
|
||||
|
|
@ -680,7 +756,7 @@ impl Prepared {
|
|||
let mut inner_size = inner_rect.size();
|
||||
|
||||
for d in 0..2 {
|
||||
inner_size[d] = match (has_bar[d], auto_shrink[d]) {
|
||||
inner_size[d] = match (scroll_enabled[d], auto_shrink[d]) {
|
||||
(true, true) => inner_size[d].min(content_size[d]), // shrink scroll area if content is small
|
||||
(true, false) => inner_size[d], // let scroll area be larger than content; fill with blank space
|
||||
(false, true) => content_size[d], // Follow the content (expand/contract to fit it).
|
||||
|
|
@ -694,14 +770,15 @@ impl Prepared {
|
|||
let outer_rect = Rect::from_min_size(inner_rect.min, inner_rect.size() + current_bar_use);
|
||||
|
||||
let content_is_too_large = [
|
||||
content_size.x > inner_rect.width(),
|
||||
content_size.y > inner_rect.height(),
|
||||
scroll_enabled[0] && inner_rect.width() < content_size.x,
|
||||
scroll_enabled[1] && inner_rect.height() < content_size.y,
|
||||
];
|
||||
|
||||
let max_offset = content_size - inner_rect.size();
|
||||
if scrolling_enabled && ui.rect_contains_pointer(outer_rect) {
|
||||
let is_hovering_outer_rect = ui.rect_contains_pointer(outer_rect);
|
||||
if scrolling_enabled && is_hovering_outer_rect {
|
||||
for d in 0..2 {
|
||||
if has_bar[d] {
|
||||
if scroll_enabled[d] {
|
||||
let scroll_delta = ui.ctx().frame_state(|fs| fs.scroll_delta);
|
||||
|
||||
let scrolling_up = state.offset[d] > 0.0 && scroll_delta[d] > 0.0;
|
||||
|
|
@ -718,39 +795,69 @@ impl Prepared {
|
|||
}
|
||||
|
||||
let show_scroll_this_frame = match scroll_bar_visibility {
|
||||
ScrollBarVisibility::AlwaysVisible => [true, true],
|
||||
ScrollBarVisibility::AlwaysHidden => [false, false],
|
||||
ScrollBarVisibility::VisibleWhenNeeded => {
|
||||
[content_is_too_large[0], content_is_too_large[1]]
|
||||
}
|
||||
ScrollBarVisibility::AlwaysHidden => [false, false],
|
||||
ScrollBarVisibility::AlwaysVisible => scroll_enabled,
|
||||
};
|
||||
|
||||
let max_scroll_bar_width = max_scroll_bar_width_with_margin(ui);
|
||||
|
||||
// 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[0] && show_bars_factor.x <= 0.0 {
|
||||
show_bars_factor.x = 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 show_scroll_this_frame[1] && show_bars_factor.y <= 0.0 {
|
||||
show_bars_factor.y = ui.ctx().animate_bool(id.with("v"), true);
|
||||
}
|
||||
|
||||
let scroll_style = ui.spacing().scroll;
|
||||
|
||||
// Paint the bars:
|
||||
for d in 0..2 {
|
||||
let animation_t = current_bar_use[1 - d] / max_scroll_bar_width;
|
||||
|
||||
if animation_t == 0.0 {
|
||||
let show_factor = show_bars_factor[d];
|
||||
if show_factor == 0.0 {
|
||||
state.scroll_bar_interaction[d] = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
// margin on either side of the scroll bar
|
||||
let inner_margin = animation_t * ui.spacing().scroll_bar_inner_margin;
|
||||
let outer_margin = animation_t * ui.spacing().scroll_bar_outer_margin;
|
||||
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 max_main = inner_rect.max[d]; // bottom of vertical scroll (d == 1)
|
||||
// left/right of a horizontal scroll (d==1)
|
||||
// top/bottom of vertical scroll (d == 1)
|
||||
let main_range = Rangef::new(inner_rect.min[d], inner_rect.max[d]);
|
||||
|
||||
if ui.clip_rect().max[1 - d] < max_cross + outer_margin {
|
||||
// Margin on either side of the scroll bar:
|
||||
let inner_margin = show_factor * scroll_style.bar_inner_margin;
|
||||
let outer_margin = show_factor * scroll_style.bar_outer_margin;
|
||||
|
||||
// top/bottom of a horizontal scroll (d==0).
|
||||
// left/rigth of a vertical scroll (d==1).
|
||||
let mut cross = if scroll_style.floating {
|
||||
let max_bar_rect = if d == 0 {
|
||||
outer_rect.with_min_y(outer_rect.max.y - scroll_style.allocated_width())
|
||||
} else {
|
||||
outer_rect.with_min_x(outer_rect.max.x - scroll_style.allocated_width())
|
||||
};
|
||||
let is_hovering_bar_area = is_hovering_outer_rect
|
||||
&& ui.rect_contains_pointer(max_bar_rect)
|
||||
|| state.scroll_bar_interaction[d];
|
||||
let is_hovering_bar_area_t = ui
|
||||
.ctx()
|
||||
.animate_bool(id.with((d, "bar_hover")), is_hovering_bar_area);
|
||||
let width = show_factor
|
||||
* lerp(
|
||||
scroll_style.floating_width..=scroll_style.bar_width,
|
||||
is_hovering_bar_area_t,
|
||||
);
|
||||
|
||||
let max_cross = outer_rect.max[1 - d] - outer_margin;
|
||||
let min_cross = max_cross - width;
|
||||
Rangef::new(min_cross, max_cross)
|
||||
} else {
|
||||
let min_cross = inner_rect.max[1 - d] + inner_margin;
|
||||
let max_cross = outer_rect.max[1 - d] - outer_margin;
|
||||
Rangef::new(min_cross, max_cross)
|
||||
};
|
||||
|
||||
if ui.clip_rect().max[1 - d] < cross.max + 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,
|
||||
|
|
@ -760,20 +867,20 @@ impl Prepared {
|
|||
// 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 width = cross.max - cross.min;
|
||||
cross.max = ui.clip_rect().max[1 - d] - outer_margin;
|
||||
cross.min = cross.max - width;
|
||||
}
|
||||
|
||||
let outer_scroll_rect = if d == 0 {
|
||||
Rect::from_min_max(
|
||||
pos2(inner_rect.left(), min_cross),
|
||||
pos2(inner_rect.right(), max_cross),
|
||||
pos2(inner_rect.left(), cross.min),
|
||||
pos2(inner_rect.right(), cross.max),
|
||||
)
|
||||
} else {
|
||||
Rect::from_min_max(
|
||||
pos2(min_cross, inner_rect.top()),
|
||||
pos2(max_cross, inner_rect.bottom()),
|
||||
pos2(cross.min, inner_rect.top()),
|
||||
pos2(cross.max, inner_rect.bottom()),
|
||||
)
|
||||
};
|
||||
|
||||
|
|
@ -782,19 +889,18 @@ impl Prepared {
|
|||
state.offset[d] = content_size[d] - inner_rect.size()[d];
|
||||
}
|
||||
|
||||
let from_content =
|
||||
|content| remap_clamp(content, 0.0..=content_size[d], min_main..=max_main);
|
||||
let from_content = |content| remap_clamp(content, 0.0..=content_size[d], main_range);
|
||||
|
||||
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),
|
||||
pos2(from_content(state.offset.x), cross.min),
|
||||
pos2(from_content(state.offset.x + inner_rect.width()), cross.max),
|
||||
)
|
||||
} else {
|
||||
Rect::from_min_max(
|
||||
pos2(min_cross, from_content(state.offset.y)),
|
||||
pos2(cross.min, from_content(state.offset.y)),
|
||||
pos2(
|
||||
max_cross,
|
||||
cross.max,
|
||||
from_content(state.offset.y + inner_rect.height()),
|
||||
),
|
||||
)
|
||||
|
|
@ -808,22 +914,24 @@ impl Prepared {
|
|||
};
|
||||
let response = ui.interact(outer_scroll_rect, interact_id, sense);
|
||||
|
||||
state.scroll_bar_interaction[d] = response.hovered() || response.dragged();
|
||||
|
||||
if let Some(pointer_pos) = response.interact_pointer_pos() {
|
||||
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[d] - handle_rect.min[d]
|
||||
} else {
|
||||
let handle_top_pos_at_bottom = max_main - handle_rect.size()[d];
|
||||
let handle_top_pos_at_bottom = main_range.max - handle_rect.size()[d];
|
||||
// Calculate the new handle top position, centering the handle on the mouse.
|
||||
let new_handle_top_pos = (pointer_pos[d] - handle_rect.size()[d] / 2.0)
|
||||
.clamp(min_main, handle_top_pos_at_bottom);
|
||||
.clamp(main_range.min, handle_top_pos_at_bottom);
|
||||
pointer_pos[d] - new_handle_top_pos
|
||||
}
|
||||
});
|
||||
|
||||
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]);
|
||||
state.offset[d] = remap(new_handle_top, main_range, 0.0..=content_size[d]);
|
||||
|
||||
// some manual action taken, scroll not stuck
|
||||
state.scroll_stuck_to_end[d] = false;
|
||||
|
|
@ -843,19 +951,19 @@ impl Prepared {
|
|||
// Avoid frame-delay by calculating a new handle rect:
|
||||
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),
|
||||
pos2(from_content(state.offset.x), cross.min),
|
||||
pos2(from_content(state.offset.x + inner_rect.width()), cross.max),
|
||||
)
|
||||
} else {
|
||||
Rect::from_min_max(
|
||||
pos2(min_cross, from_content(state.offset.y)),
|
||||
pos2(cross.min, from_content(state.offset.y)),
|
||||
pos2(
|
||||
max_cross,
|
||||
cross.max,
|
||||
from_content(state.offset.y + inner_rect.height()),
|
||||
),
|
||||
)
|
||||
};
|
||||
let min_handle_size = ui.spacing().scroll_handle_min_length;
|
||||
let min_handle_size = scroll_style.handle_min_length;
|
||||
if handle_rect.size()[d] < min_handle_size {
|
||||
handle_rect = Rect::from_center_size(
|
||||
handle_rect.center(),
|
||||
|
|
@ -868,21 +976,76 @@ impl Prepared {
|
|||
}
|
||||
|
||||
let visuals = if scrolling_enabled {
|
||||
ui.style().interact(&response)
|
||||
// Pick visuals based on interaction with the handle.
|
||||
// Remember that the response is for the whole scroll bar!
|
||||
let is_hovering_handle = response.hovered()
|
||||
&& ui.input(|i| {
|
||||
i.pointer
|
||||
.latest_pos()
|
||||
.map_or(false, |p| handle_rect.contains(p))
|
||||
});
|
||||
let visuals = ui.visuals();
|
||||
if response.is_pointer_button_down_on() {
|
||||
&visuals.widgets.active
|
||||
} else if is_hovering_handle {
|
||||
&visuals.widgets.hovered
|
||||
} else {
|
||||
&visuals.widgets.inactive
|
||||
}
|
||||
} else {
|
||||
&ui.style().visuals.widgets.inactive
|
||||
&ui.visuals().widgets.inactive
|
||||
};
|
||||
|
||||
let handle_opacity = if scroll_style.floating {
|
||||
if response.hovered() || response.dragged() {
|
||||
scroll_style.interact_handle_opacity
|
||||
} else {
|
||||
let is_hovering_outer_rect_t = ui.ctx().animate_bool(
|
||||
id.with((d, "is_hovering_outer_rect")),
|
||||
is_hovering_outer_rect,
|
||||
);
|
||||
lerp(
|
||||
scroll_style.dormant_handle_opacity
|
||||
..=scroll_style.active_handle_opacity,
|
||||
is_hovering_outer_rect_t,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
|
||||
let background_opacity = if scroll_style.floating {
|
||||
if response.hovered() || response.dragged() {
|
||||
scroll_style.interact_background_opacity
|
||||
} else if is_hovering_outer_rect {
|
||||
scroll_style.active_background_opacity
|
||||
} else {
|
||||
scroll_style.dormant_background_opacity
|
||||
}
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
|
||||
let handle_color = if scroll_style.foreground_color {
|
||||
visuals.fg_stroke.color
|
||||
} else {
|
||||
visuals.bg_fill
|
||||
};
|
||||
|
||||
// Background:
|
||||
ui.painter().add(epaint::Shape::rect_filled(
|
||||
outer_scroll_rect,
|
||||
visuals.rounding,
|
||||
ui.visuals().extreme_bg_color,
|
||||
ui.visuals()
|
||||
.extreme_bg_color
|
||||
.gamma_multiply(background_opacity),
|
||||
));
|
||||
|
||||
// Handle:
|
||||
ui.painter().add(epaint::Shape::rect_filled(
|
||||
handle_rect,
|
||||
visuals.rounding,
|
||||
visuals.bg_fill,
|
||||
handle_color.gamma_multiply(handle_opacity),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
@ -904,9 +1067,9 @@ impl Prepared {
|
|||
// has appropriate effect.
|
||||
state.scroll_stuck_to_end = [
|
||||
(state.offset[0] == available_offset[0])
|
||||
|| (self.stick_to_end[0] && available_offset[0] < 0.),
|
||||
|| (self.stick_to_end[0] && available_offset[0] < 0.0),
|
||||
(state.offset[1] == available_offset[1])
|
||||
|| (self.stick_to_end[1] && available_offset[1] < 0.),
|
||||
|| (self.stick_to_end[1] && available_offset[1] < 0.0),
|
||||
];
|
||||
|
||||
state.show_scroll = show_scroll_this_frame;
|
||||
|
|
@ -917,10 +1080,3 @@ impl Prepared {
|
|||
(content_size, 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().scroll_bar_inner_margin
|
||||
+ ui.spacing().scroll_bar_width
|
||||
+ ui.spacing().scroll_bar_outer_margin
|
||||
}
|
||||
|
|
|
|||
|
|
@ -419,7 +419,7 @@ impl<'open> Window<'open> {
|
|||
ui.add_space(title_content_spacing);
|
||||
}
|
||||
|
||||
if scroll.has_any_bar() {
|
||||
if scroll.is_any_scroll_enabled() {
|
||||
scroll.show(ui, add_contents).inner
|
||||
} else {
|
||||
add_contents(ui)
|
||||
|
|
|
|||
|
|
@ -2,11 +2,13 @@
|
|||
|
||||
#![allow(clippy::if_same_then_else)]
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use epaint::{Rounding, Shadow, Stroke};
|
||||
|
||||
use crate::{
|
||||
ecolor::*, emath::*, ComboBox, CursorIcon, FontFamily, FontId, Response, RichText, WidgetText,
|
||||
};
|
||||
use epaint::{Rounding, Shadow, Stroke};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
|
|
@ -303,16 +305,8 @@ pub struct Spacing {
|
|||
/// Height of a combo-box before showing scroll bars.
|
||||
pub combo_height: f32,
|
||||
|
||||
pub scroll_bar_width: f32,
|
||||
|
||||
/// Make sure the scroll handle is at least this big
|
||||
pub scroll_handle_min_length: 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,
|
||||
/// Controls the spacing of a [`crate::ScrollArea`].
|
||||
pub scroll: ScrollStyle,
|
||||
}
|
||||
|
||||
impl Spacing {
|
||||
|
|
@ -333,6 +327,277 @@ impl Spacing {
|
|||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// Controls the spacing and visuals of a [`crate::ScrollArea`].
|
||||
///
|
||||
/// There are three presets to chose from:
|
||||
/// * [`Self::solid`]
|
||||
/// * [`Self::thin`]
|
||||
/// * [`Self::floating`]
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
#[cfg_attr(feature = "serde", serde(default))]
|
||||
pub struct ScrollStyle {
|
||||
/// If `true`, scroll bars float above the content, partially covering it.
|
||||
///
|
||||
/// If `false`, the scroll bars allocate space, shrinking the area
|
||||
/// available to the contents.
|
||||
///
|
||||
/// This also changes the colors of the scroll-handle to make
|
||||
/// it more promiment.
|
||||
pub floating: bool,
|
||||
|
||||
/// The width of the scroll bars at it largest.
|
||||
pub bar_width: f32,
|
||||
|
||||
/// Make sure the scroll handle is at least this big
|
||||
pub handle_min_length: f32,
|
||||
|
||||
/// Margin between contents and scroll bar.
|
||||
pub bar_inner_margin: f32,
|
||||
|
||||
/// Margin between scroll bar and the outer container (e.g. right of a vertical scroll bar).
|
||||
/// Only makes sense for non-floating scroll bars.
|
||||
pub bar_outer_margin: f32,
|
||||
|
||||
/// The thin width of floating scroll bars that the user is NOT hovering.
|
||||
///
|
||||
/// When the user hovers the scroll bars they expand to [`Self::bar_width`].
|
||||
pub floating_width: f32,
|
||||
|
||||
/// How much space i allocated for a floating scroll bar?
|
||||
///
|
||||
/// Normally this is zero, but you could set this to something small
|
||||
/// like 4.0 and set [`Self::dormant_handle_opacity`] and
|
||||
/// [`Self::dormant_background_opacity`] to e.g. 0.5
|
||||
/// so as to always show a thin scroll bar.
|
||||
pub floating_allocated_width: f32,
|
||||
|
||||
/// If true, use colors with more contrast. Good for floating scroll bars.
|
||||
pub foreground_color: bool,
|
||||
|
||||
/// The opaqueness of the background when the user is neither scrolling
|
||||
/// nor hovering the scroll area.
|
||||
///
|
||||
/// This is only for floating scroll bars.
|
||||
/// Solid scroll bars are always opaque.
|
||||
pub dormant_background_opacity: f32,
|
||||
|
||||
/// The opaqueness of the background when the user is hovering
|
||||
/// the scroll area, but not the scroll bar.
|
||||
///
|
||||
/// This is only for floating scroll bars.
|
||||
/// Solid scroll bars are always opaque.
|
||||
pub active_background_opacity: f32,
|
||||
|
||||
/// The opaqueness of the background when the user is hovering
|
||||
/// over the scroll bars.
|
||||
///
|
||||
/// This is only for floating scroll bars.
|
||||
/// Solid scroll bars are always opaque.
|
||||
pub interact_background_opacity: f32,
|
||||
|
||||
/// The opaqueness of the handle when the user is neither scrolling
|
||||
/// nor hovering the scroll area.
|
||||
///
|
||||
/// This is only for floating scroll bars.
|
||||
/// Solid scroll bars are always opaque.
|
||||
pub dormant_handle_opacity: f32,
|
||||
|
||||
/// The opaqueness of the handle when the user is hovering
|
||||
/// the scroll area, but not the scroll bar.
|
||||
///
|
||||
/// This is only for floating scroll bars.
|
||||
/// Solid scroll bars are always opaque.
|
||||
pub active_handle_opacity: f32,
|
||||
|
||||
/// The opaqueness of the handle when the user is hovering
|
||||
/// over the scroll bars.
|
||||
///
|
||||
/// This is only for floating scroll bars.
|
||||
/// Solid scroll bars are always opaque.
|
||||
pub interact_handle_opacity: f32,
|
||||
}
|
||||
|
||||
impl Default for ScrollStyle {
|
||||
fn default() -> Self {
|
||||
Self::floating()
|
||||
}
|
||||
}
|
||||
|
||||
impl ScrollStyle {
|
||||
/// Solid scroll bars that always use up space
|
||||
pub fn solid() -> Self {
|
||||
Self {
|
||||
floating: false,
|
||||
bar_width: 6.0,
|
||||
handle_min_length: 12.0,
|
||||
bar_inner_margin: 4.0,
|
||||
bar_outer_margin: 0.0,
|
||||
floating_width: 2.0,
|
||||
floating_allocated_width: 0.0,
|
||||
|
||||
foreground_color: false,
|
||||
|
||||
dormant_background_opacity: 0.0,
|
||||
active_background_opacity: 0.4,
|
||||
interact_background_opacity: 0.7,
|
||||
|
||||
dormant_handle_opacity: 0.0,
|
||||
active_handle_opacity: 0.6,
|
||||
interact_handle_opacity: 1.0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Thin scroll bars that expand on hover
|
||||
pub fn thin() -> Self {
|
||||
Self {
|
||||
floating: true,
|
||||
bar_width: 12.0,
|
||||
floating_allocated_width: 6.0,
|
||||
foreground_color: false,
|
||||
|
||||
dormant_background_opacity: 1.0,
|
||||
dormant_handle_opacity: 1.0,
|
||||
|
||||
active_background_opacity: 1.0,
|
||||
active_handle_opacity: 1.0,
|
||||
|
||||
// Be tranlucent when expanded so we can see the content
|
||||
interact_background_opacity: 0.6,
|
||||
interact_handle_opacity: 0.6,
|
||||
|
||||
..Self::solid()
|
||||
}
|
||||
}
|
||||
|
||||
/// No scroll bars until you hover the scroll area,
|
||||
/// at which time they appear faintly, and then expand
|
||||
/// when you hover the scroll bars.
|
||||
pub fn floating() -> Self {
|
||||
Self {
|
||||
floating: true,
|
||||
bar_width: 12.0,
|
||||
foreground_color: true,
|
||||
floating_allocated_width: 0.0,
|
||||
dormant_background_opacity: 0.0,
|
||||
dormant_handle_opacity: 0.0,
|
||||
..Self::solid()
|
||||
}
|
||||
}
|
||||
|
||||
/// Width of a solid vertical scrollbar, or height of a horizontal scroll bar, when it is at its widest.
|
||||
pub fn allocated_width(&self) -> f32 {
|
||||
if self.floating {
|
||||
self.floating_allocated_width
|
||||
} else {
|
||||
self.bar_inner_margin + self.bar_width + self.bar_outer_margin
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ui(&mut self, ui: &mut Ui) {
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Presets:");
|
||||
ui.selectable_value(self, Self::solid(), "Solid");
|
||||
ui.selectable_value(self, Self::thin(), "Thin");
|
||||
ui.selectable_value(self, Self::floating(), "Floating");
|
||||
});
|
||||
|
||||
ui.collapsing("Details", |ui| {
|
||||
self.details_ui(ui);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn details_ui(&mut self, ui: &mut Ui) {
|
||||
let Self {
|
||||
floating,
|
||||
bar_width,
|
||||
handle_min_length,
|
||||
bar_inner_margin,
|
||||
bar_outer_margin,
|
||||
floating_width,
|
||||
floating_allocated_width,
|
||||
|
||||
foreground_color,
|
||||
|
||||
dormant_background_opacity,
|
||||
active_background_opacity,
|
||||
interact_background_opacity,
|
||||
dormant_handle_opacity,
|
||||
active_handle_opacity,
|
||||
interact_handle_opacity,
|
||||
} = self;
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Type:");
|
||||
ui.selectable_value(floating, false, "Solid");
|
||||
ui.selectable_value(floating, true, "Floating");
|
||||
});
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.add(DragValue::new(bar_width).clamp_range(0.0..=32.0));
|
||||
ui.label("Full bar width");
|
||||
});
|
||||
if *floating {
|
||||
ui.horizontal(|ui| {
|
||||
ui.add(DragValue::new(floating_width).clamp_range(0.0..=32.0));
|
||||
ui.label("Thin bar width");
|
||||
});
|
||||
ui.horizontal(|ui| {
|
||||
ui.add(DragValue::new(floating_allocated_width).clamp_range(0.0..=32.0));
|
||||
ui.label("Allocated width");
|
||||
});
|
||||
}
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.add(DragValue::new(handle_min_length).clamp_range(0.0..=32.0));
|
||||
ui.label("Minimum handle length");
|
||||
});
|
||||
ui.horizontal(|ui| {
|
||||
ui.add(DragValue::new(bar_outer_margin).clamp_range(0.0..=32.0));
|
||||
ui.label("Outer margin");
|
||||
});
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Color:");
|
||||
ui.selectable_value(foreground_color, false, "Background");
|
||||
ui.selectable_value(foreground_color, true, "Foreground");
|
||||
});
|
||||
|
||||
if *floating {
|
||||
crate::Grid::new("opacity").show(ui, |ui| {
|
||||
fn opacity_ui(ui: &mut Ui, opacity: &mut f32) {
|
||||
ui.add(DragValue::new(opacity).speed(0.01).clamp_range(0.0..=1.0));
|
||||
}
|
||||
|
||||
ui.label("Opacity");
|
||||
ui.label("Dormant");
|
||||
ui.label("Active");
|
||||
ui.label("Interacting");
|
||||
ui.end_row();
|
||||
|
||||
ui.label("Background:");
|
||||
opacity_ui(ui, dormant_background_opacity);
|
||||
opacity_ui(ui, active_background_opacity);
|
||||
opacity_ui(ui, interact_background_opacity);
|
||||
ui.end_row();
|
||||
|
||||
ui.label("Handle:");
|
||||
opacity_ui(ui, dormant_handle_opacity);
|
||||
opacity_ui(ui, active_handle_opacity);
|
||||
opacity_ui(ui, interact_handle_opacity);
|
||||
ui.end_row();
|
||||
});
|
||||
} else {
|
||||
ui.horizontal(|ui| {
|
||||
ui.add(DragValue::new(bar_inner_margin).clamp_range(0.0..=32.0));
|
||||
ui.label("Inner margin");
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct Margin {
|
||||
|
|
@ -807,10 +1072,7 @@ impl Default for Spacing {
|
|||
icon_spacing: 4.0,
|
||||
tooltip_width: 600.0,
|
||||
combo_height: 200.0,
|
||||
scroll_bar_width: 8.0,
|
||||
scroll_handle_min_length: 12.0,
|
||||
scroll_bar_inner_margin: 4.0,
|
||||
scroll_bar_outer_margin: 0.0,
|
||||
scroll: Default::default(),
|
||||
indent_ends_with_horizontal_line: false,
|
||||
}
|
||||
}
|
||||
|
|
@ -1146,10 +1408,7 @@ impl Spacing {
|
|||
tooltip_width,
|
||||
indent_ends_with_horizontal_line,
|
||||
combo_height,
|
||||
scroll_bar_width,
|
||||
scroll_handle_min_length,
|
||||
scroll_bar_inner_margin,
|
||||
scroll_bar_outer_margin,
|
||||
scroll,
|
||||
} = self;
|
||||
|
||||
ui.add(slider_vec2(item_spacing, 0.0..=20.0, "Item spacing"));
|
||||
|
|
@ -1176,21 +1435,9 @@ impl Spacing {
|
|||
ui.add(DragValue::new(text_edit_width).clamp_range(0.0..=1000.0));
|
||||
ui.label("TextEdit width");
|
||||
});
|
||||
ui.horizontal(|ui| {
|
||||
ui.add(DragValue::new(scroll_bar_width).clamp_range(0.0..=32.0));
|
||||
ui.label("Scroll-bar width");
|
||||
});
|
||||
ui.horizontal(|ui| {
|
||||
ui.add(DragValue::new(scroll_handle_min_length).clamp_range(0.0..=32.0));
|
||||
ui.label("Scroll-bar handle min length");
|
||||
});
|
||||
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.collapsing("Scroll Area", |ui| {
|
||||
scroll.ui(ui);
|
||||
});
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
|
|
|
|||
|
|
@ -371,7 +371,7 @@ impl BoxPainting {
|
|||
ui.painter().rect(
|
||||
rect,
|
||||
self.rounding,
|
||||
Color32::from_gray(64),
|
||||
ui.visuals().text_color().gamma_multiply(0.5),
|
||||
Stroke::new(self.stroke_width, Color32::WHITE),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
use egui::*;
|
||||
use egui::{scroll_area::ScrollBarVisibility, *};
|
||||
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
enum ScrollDemo {
|
||||
ScrollAppearance,
|
||||
ScrollTo,
|
||||
ManyLines,
|
||||
LargeCanvas,
|
||||
|
|
@ -12,7 +13,7 @@ enum ScrollDemo {
|
|||
|
||||
impl Default for ScrollDemo {
|
||||
fn default() -> Self {
|
||||
Self::ScrollTo
|
||||
Self::ScrollAppearance
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -20,6 +21,7 @@ impl Default for ScrollDemo {
|
|||
#[cfg_attr(feature = "serde", serde(default))]
|
||||
#[derive(Default, PartialEq)]
|
||||
pub struct Scrolling {
|
||||
appearance: ScrollAppearance,
|
||||
demo: ScrollDemo,
|
||||
scroll_to: ScrollTo,
|
||||
scroll_stick_to: ScrollStickTo,
|
||||
|
|
@ -33,7 +35,9 @@ impl super::Demo for Scrolling {
|
|||
fn show(&mut self, ctx: &egui::Context, open: &mut bool) {
|
||||
egui::Window::new(self.name())
|
||||
.open(open)
|
||||
.resizable(false)
|
||||
.resizable(true)
|
||||
.hscroll(false)
|
||||
.vscroll(false)
|
||||
.show(ctx, |ui| {
|
||||
use super::View as _;
|
||||
self.ui(ui);
|
||||
|
|
@ -44,6 +48,7 @@ impl super::Demo for Scrolling {
|
|||
impl super::View for Scrolling {
|
||||
fn ui(&mut self, ui: &mut Ui) {
|
||||
ui.horizontal(|ui| {
|
||||
ui.selectable_value(&mut self.demo, ScrollDemo::ScrollAppearance, "Appearance");
|
||||
ui.selectable_value(&mut self.demo, ScrollDemo::ScrollTo, "Scroll to");
|
||||
ui.selectable_value(
|
||||
&mut self.demo,
|
||||
|
|
@ -60,6 +65,9 @@ impl super::View for Scrolling {
|
|||
});
|
||||
ui.separator();
|
||||
match self.demo {
|
||||
ScrollDemo::ScrollAppearance => {
|
||||
self.appearance.ui(ui);
|
||||
}
|
||||
ScrollDemo::ScrollTo => {
|
||||
self.scroll_to.ui(ui);
|
||||
}
|
||||
|
|
@ -84,6 +92,75 @@ impl super::View for Scrolling {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
#[cfg_attr(feature = "serde", serde(default))]
|
||||
#[derive(PartialEq)]
|
||||
struct ScrollAppearance {
|
||||
num_lorem_ipsums: usize,
|
||||
visibility: ScrollBarVisibility,
|
||||
}
|
||||
|
||||
impl Default for ScrollAppearance {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
num_lorem_ipsums: 2,
|
||||
visibility: ScrollBarVisibility::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ScrollAppearance {
|
||||
fn ui(&mut self, ui: &mut egui::Ui) {
|
||||
let Self {
|
||||
num_lorem_ipsums,
|
||||
visibility,
|
||||
} = self;
|
||||
|
||||
let mut style: Style = (*ui.ctx().style()).clone();
|
||||
|
||||
style.spacing.scroll.ui(ui);
|
||||
|
||||
ui.add_space(8.0);
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("ScrollBarVisibility:");
|
||||
for option in ScrollBarVisibility::ALL {
|
||||
ui.selectable_value(visibility, option, format!("{option:?}"));
|
||||
}
|
||||
});
|
||||
ui.weak("When to show scroll bars; resize the window to see the effect.");
|
||||
|
||||
ui.add_space(8.0);
|
||||
|
||||
ui.ctx().set_style(style.clone());
|
||||
ui.set_style(style);
|
||||
|
||||
ui.separator();
|
||||
|
||||
ui.add(
|
||||
egui::Slider::new(num_lorem_ipsums, 1..=100)
|
||||
.text("Content length")
|
||||
.logarithmic(true),
|
||||
);
|
||||
|
||||
ui.separator();
|
||||
|
||||
ScrollArea::vertical()
|
||||
.auto_shrink([false; 2])
|
||||
.scroll_bar_visibility(*visibility)
|
||||
.show(ui, |ui| {
|
||||
ui.with_layout(
|
||||
egui::Layout::top_down(egui::Align::LEFT).with_cross_justify(true),
|
||||
|ui| {
|
||||
for _ in 0..*num_lorem_ipsums {
|
||||
ui.label(crate::LOREM_IPSUM_LONG);
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn huge_content_lines(ui: &mut egui::Ui) {
|
||||
ui.label(
|
||||
"A lot of rows, but only the visible ones are laid out, so performance is still good:",
|
||||
|
|
|
|||
|
|
@ -366,9 +366,9 @@ impl<'a> TableBuilder<'a> {
|
|||
fn available_width(&self) -> f32 {
|
||||
self.ui.available_rect_before_wrap().width()
|
||||
- if self.scroll_options.vscroll {
|
||||
self.ui.spacing().scroll_bar_inner_margin
|
||||
+ self.ui.spacing().scroll_bar_width
|
||||
+ self.ui.spacing().scroll_bar_outer_margin
|
||||
self.ui.spacing().scroll.bar_inner_margin
|
||||
+ self.ui.spacing().scroll.bar_width
|
||||
+ self.ui.spacing().scroll.bar_outer_margin
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
|
|
|
|||
|
|
@ -150,6 +150,34 @@ impl Rect {
|
|||
rect
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn with_min_x(mut self, min_x: f32) -> Self {
|
||||
self.min.x = min_x;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn with_min_y(mut self, min_y: f32) -> Self {
|
||||
self.min.y = min_y;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn with_max_x(mut self, max_x: f32) -> Self {
|
||||
self.max.x = max_x;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn with_max_y(mut self, max_y: f32) -> Self {
|
||||
self.max.y = max_y;
|
||||
self
|
||||
}
|
||||
|
||||
/// Expand by this much in each direction, keeping the center
|
||||
#[must_use]
|
||||
pub fn expand(self, amnt: f32) -> Self {
|
||||
|
|
|
|||
|
|
@ -274,6 +274,16 @@ impl Vec2 {
|
|||
self.x.max(self.y)
|
||||
}
|
||||
|
||||
/// Swizzle the axes.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn yx(self) -> Vec2 {
|
||||
Vec2 {
|
||||
x: self.y,
|
||||
y: self.x,
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
#[inline]
|
||||
pub fn clamp(self, min: Self, max: Self) -> Self {
|
||||
|
|
|
|||
|
|
@ -535,6 +535,7 @@ pub mod path {
|
|||
add_circle_quadrant(path, pos2(min.x + r.sw, max.y - r.sw), r.sw, 1.0);
|
||||
add_circle_quadrant(path, pos2(min.x + r.nw, min.y + r.nw), r.nw, 2.0);
|
||||
add_circle_quadrant(path, pos2(max.x - r.ne, min.y + r.ne), r.ne, 3.0);
|
||||
path.dedup(); // We get duplicates for thin rectangles, producing visual artifats
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue