pub struct Scroll<T, W> { /* private fields */ }
Expand description
A container that scrolls its contents.
This container holds a single child, and uses the wheel to scroll it when the child’s bounds are larger than the viewport.
The child is laid out with completely unconstrained layout bounds by
default. Restrict to a specific axis with vertical
or horizontal
.
When restricted to scrolling on a specific axis the child’s size is
locked on the opposite axis.
Implementations§
source§impl<T, W: Widget<T>> Scroll<T, W>
impl<T, W: Widget<T>> Scroll<T, W>
sourcepub fn new(child: W) -> Scroll<T, W>
pub fn new(child: W) -> Scroll<T, W>
Create a new scroll container.
This method will allow scrolling in all directions if child’s bounds are larger than the viewport. Use vertical and horizontal methods to limit scrolling to a specific axis.
Examples found in repository?
More examples
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
fn build_app() -> impl Widget<u32> {
let mut col = Flex::column();
let rows = 30;
let cols = 30;
for i in 0..cols {
let mut row = Flex::row();
let col_progress = i as f64 / cols as f64;
for j in 0..rows {
let row_progress = j as f64 / rows as f64;
row.add_child(
Container::new(SizedBox::empty().width(200.0).height(200.0))
.background(Color::rgb(1.0 * col_progress, 1.0 * row_progress, 1.0)),
);
}
col.add_child(row);
}
Scroll::new(col)
}
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135
fn build_root_widget() -> impl Widget<AppState> {
let label = Scroll::new(
RawLabel::new()
.with_text_color(Color::BLACK)
.with_line_break_mode(LineBreaking::WordWrap)
.lens(AppState::rendered)
.expand_width()
.padding((SPACER_SIZE * 4.0, SPACER_SIZE)),
)
.vertical()
.background(Color::grey8(222))
.expand();
let textbox = TextBox::multiline()
.lens(AppState::raw)
.controller(RichTextRebuilder)
.expand()
.padding(5.0);
Split::columns(label, textbox)
}
264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301
fn event_list() -> impl Widget<AppState> {
// Because this would be a HUGE block of repeated code with constants
// we just use a loop to generate the header.
let mut header = Flex::row().with_child(
Label::new(PROPERTIES[0].0)
.fix_width(PROPERTIES[0].1)
.background(HEADER_BACKGROUND),
);
for (name, size) in PROPERTIES.iter().skip(1) {
// Keep in mind that later on, in the main function,
// we set the default spacer values. Without explicitly
// setting them the default spacer is bigger, and is
// probably not desirable for your purposes.
header.add_default_spacer();
header.add_child(
Label::new(*name)
.fix_width(*size)
.background(HEADER_BACKGROUND),
);
}
Scroll::new(
Flex::column()
.cross_axis_alignment(CrossAxisAlignment::Start)
.with_child(header)
.with_default_spacer()
.with_flex_child(
// `List::new` generates a list entry for every element in the `Vec`.
// In this case it shows a log entry for every element in `AppState::events`.
// `make_list_item` generates this new log entry.
Scroll::new(List::new(make_list_item).lens(AppState::events)).vertical(),
1.0,
)
.background(Color::WHITE),
)
.horizontal()
.padding(10.0)
}
111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155
fn build_root_widget() -> impl Widget<AppState> {
let label = Scroll::new(
RawLabel::new()
.with_text_color(Color::BLACK)
.controller(LabelController)
.background(Color::WHITE)
.expand_width()
.padding((SPACER_SIZE * 4.0, SPACER_SIZE))
.background(Color::grey8(222)),
)
.vertical();
let line_break_chooser = Flex::column()
.with_child(Label::new("Line break mode"))
.with_spacer(SPACER_SIZE)
.with_child(RadioGroup::column(vec![
("Clip", LineBreaking::Clip),
("Wrap", LineBreaking::WordWrap),
("Overflow", LineBreaking::Overflow),
]))
.lens(AppState::line_break_mode);
let alignment_picker = Flex::column()
.with_child(Label::new("Justification"))
.with_spacer(SPACER_SIZE)
.with_child(RadioGroup::column(vec![
("Start", TextAlignment::Start),
("End", TextAlignment::End),
("Center", TextAlignment::Center),
("Justified", TextAlignment::Justified),
]))
.lens(AppState::alignment);
let controls = Flex::row()
.cross_axis_alignment(druid::widget::CrossAxisAlignment::Start)
.with_child(alignment_picker)
.with_spacer(SPACER_SIZE)
.with_child(line_break_chooser)
.padding(SPACER_SIZE);
Flex::column()
.cross_axis_alignment(druid::widget::CrossAxisAlignment::Start)
.with_child(controls)
.with_flex_child(label, 1.0)
}
72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
fn ui_builder() -> impl Widget<AppData> {
let my_painter = Painter::new(|ctx, _, _| {
let bounds = ctx.size().to_rect();
if ctx.is_hot() {
ctx.fill(bounds, &Color::rgba8(0, 0, 0, 128));
}
if ctx.is_active() {
ctx.stroke(bounds, &Color::WHITE, 2.0);
}
});
// This is Druid's default text style.
// It's set by theme::LABEL_COLOR and theme::UI_FONT
let label =
Label::new(|data: &String, _env: &_| format!("Default: {data}")).lens(AppData::text);
// The text_color, text_size, and font builder methods can override the
// defaults provided by the theme by passing in a Key or a concrete value.
//
// In this example, text_color receives a Key from the theme, text_size
// gets a custom key which we set with the env_scope wrapper, and the
// default font key (theme::FONT_NAME) is overridden in the env_scope
// wrapper. (Like text_color and text_size, the font can be set using the
// with_font builder method, but overriding here makes it easy to fall back
// to the default font)
let styled_label = Label::new(|data: &AppData, _env: &_| format!("{data}"))
.with_text_color(theme::PRIMARY_LIGHT)
.with_font(MY_CUSTOM_FONT)
.background(my_painter)
.on_click(|_, data, _| {
data.size *= 1.1;
})
.env_scope(|env: &mut druid::Env, data: &AppData| {
let new_font = if data.mono {
FontDescriptor::new(FontFamily::MONOSPACE)
} else {
FontDescriptor::new(FontFamily::SYSTEM_UI)
}
.with_size(data.size);
env.set(MY_CUSTOM_FONT, new_font);
});
let labels = Scroll::new(
Flex::column()
.cross_axis_alignment(CrossAxisAlignment::Start)
.with_child(label)
.with_default_spacer()
.with_child(styled_label),
)
.expand_height()
.fix_width(COLUMN_WIDTH);
let stepper = Stepper::new()
.with_range(0.0, 100.0)
.with_step(1.0)
.with_wraparound(false)
.lens(AppData::size);
// TODO: Replace Parse usage with TextBox::with_formatter
#[allow(deprecated)]
let stepper_textbox = LensWrap::new(
Parse::new(TextBox::new()),
AppData::size.map(|x| Some(*x), |x, y| *x = y.unwrap_or(24.0)),
);
let mono_checkbox = Checkbox::new("Monospace").lens(AppData::mono);
let stepper_row = Flex::row()
.with_child(stepper_textbox)
.with_child(stepper)
.with_default_spacer()
.with_child(mono_checkbox);
let input = TextBox::multiline()
.with_placeholder("Your sample text here :)")
.fix_width(COLUMN_WIDTH)
.fix_height(140.0)
.lens(AppData::text);
Flex::column()
.main_axis_alignment(MainAxisAlignment::Center)
.with_default_spacer()
.with_flex_child(labels, 1.0)
.with_default_spacer()
.with_child(input)
.with_default_spacer()
.with_child(stepper_row)
.with_default_spacer()
}
sourcepub fn scroll_by<C: ChangeCtx>(&mut self, ctx: &mut C, delta: Vec2) -> bool
pub fn scroll_by<C: ChangeCtx>(&mut self, ctx: &mut C, delta: Vec2) -> bool
Scroll by delta
units.
Returns true
if the scroll offset has changed.
source§impl<T, W> Scroll<T, W>
impl<T, W> Scroll<T, W>
sourcepub fn vertical(self) -> Self
pub fn vertical(self) -> Self
Restrict scrolling to the vertical axis while locking child width.
Examples found in repository?
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135
fn build_root_widget() -> impl Widget<AppState> {
let label = Scroll::new(
RawLabel::new()
.with_text_color(Color::BLACK)
.with_line_break_mode(LineBreaking::WordWrap)
.lens(AppState::rendered)
.expand_width()
.padding((SPACER_SIZE * 4.0, SPACER_SIZE)),
)
.vertical()
.background(Color::grey8(222))
.expand();
let textbox = TextBox::multiline()
.lens(AppState::raw)
.controller(RichTextRebuilder)
.expand()
.padding(5.0);
Split::columns(label, textbox)
}
More examples
264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301
fn event_list() -> impl Widget<AppState> {
// Because this would be a HUGE block of repeated code with constants
// we just use a loop to generate the header.
let mut header = Flex::row().with_child(
Label::new(PROPERTIES[0].0)
.fix_width(PROPERTIES[0].1)
.background(HEADER_BACKGROUND),
);
for (name, size) in PROPERTIES.iter().skip(1) {
// Keep in mind that later on, in the main function,
// we set the default spacer values. Without explicitly
// setting them the default spacer is bigger, and is
// probably not desirable for your purposes.
header.add_default_spacer();
header.add_child(
Label::new(*name)
.fix_width(*size)
.background(HEADER_BACKGROUND),
);
}
Scroll::new(
Flex::column()
.cross_axis_alignment(CrossAxisAlignment::Start)
.with_child(header)
.with_default_spacer()
.with_flex_child(
// `List::new` generates a list entry for every element in the `Vec`.
// In this case it shows a log entry for every element in `AppState::events`.
// `make_list_item` generates this new log entry.
Scroll::new(List::new(make_list_item).lens(AppState::events)).vertical(),
1.0,
)
.background(Color::WHITE),
)
.horizontal()
.padding(10.0)
}
111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155
fn build_root_widget() -> impl Widget<AppState> {
let label = Scroll::new(
RawLabel::new()
.with_text_color(Color::BLACK)
.controller(LabelController)
.background(Color::WHITE)
.expand_width()
.padding((SPACER_SIZE * 4.0, SPACER_SIZE))
.background(Color::grey8(222)),
)
.vertical();
let line_break_chooser = Flex::column()
.with_child(Label::new("Line break mode"))
.with_spacer(SPACER_SIZE)
.with_child(RadioGroup::column(vec![
("Clip", LineBreaking::Clip),
("Wrap", LineBreaking::WordWrap),
("Overflow", LineBreaking::Overflow),
]))
.lens(AppState::line_break_mode);
let alignment_picker = Flex::column()
.with_child(Label::new("Justification"))
.with_spacer(SPACER_SIZE)
.with_child(RadioGroup::column(vec![
("Start", TextAlignment::Start),
("End", TextAlignment::End),
("Center", TextAlignment::Center),
("Justified", TextAlignment::Justified),
]))
.lens(AppState::alignment);
let controls = Flex::row()
.cross_axis_alignment(druid::widget::CrossAxisAlignment::Start)
.with_child(alignment_picker)
.with_spacer(SPACER_SIZE)
.with_child(line_break_chooser)
.padding(SPACER_SIZE);
Flex::column()
.cross_axis_alignment(druid::widget::CrossAxisAlignment::Start)
.with_child(controls)
.with_flex_child(label, 1.0)
}
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
fn ui_builder() -> impl Widget<AppData> {
let mut root = Flex::column();
// Build a button to add children to both lists
root.add_child(
Button::new("Add")
.on_click(|_, data: &mut AppData, _| {
// Add child to left list
data.l_index += 1;
data.left.push_back(data.l_index as u32);
// Add child to right list
data.r_index += 1;
data.right.push_back(data.r_index as u32);
})
.fix_height(30.0)
.expand_width(),
);
let mut lists = Flex::row().cross_axis_alignment(CrossAxisAlignment::Start);
// Build a simple list
lists.add_flex_child(
Scroll::new(List::new(|| {
Label::new(|item: &u32, _env: &_| format!("List item #{item}"))
.align_vertical(UnitPoint::LEFT)
.padding(10.0)
.expand()
.height(50.0)
.background(Color::rgb(0.5, 0.5, 0.5))
}))
.vertical()
.lens(AppData::left),
1.0,
);
// Build a list with shared data
lists.add_flex_child(
Scroll::new(
List::new(|| {
Flex::row()
.with_child(
Label::new(|(_, item): &(Vector<u32>, u32), _env: &_| {
format!("List item #{item}")
})
.align_vertical(UnitPoint::LEFT),
)
.with_flex_spacer(1.0)
.with_child(
Button::new("Delete")
.on_click(|_ctx, (shared, item): &mut (Vector<u32>, u32), _env| {
// We have access to both child's data and shared data.
// Remove element from right list.
shared.retain(|v| v != item);
})
.fix_size(80.0, 20.0)
.align_vertical(UnitPoint::CENTER),
)
.padding(10.0)
.background(Color::rgb(0.5, 0.0, 0.5))
.fix_height(50.0)
})
.with_spacing(10.),
)
.vertical()
.lens(lens::Identity.map(
// Expose shared data with children data
|d: &AppData| (d.right.clone(), d.right.clone()),
|d: &mut AppData, x: (Vector<u32>, Vector<u32>)| {
// If shared data was changed reflect the changes in our AppData
d.right = x.0
},
)),
1.0,
);
root.add_flex_child(lists, 1.0);
root.with_child(Label::new("horizontal list"))
.with_child(
Scroll::new(
List::new(|| {
Label::new(|item: &u32, _env: &_| format!("List item #{item}"))
.padding(10.0)
.background(Color::rgb(0.5, 0.5, 0.0))
.fix_height(50.0)
})
.horizontal()
.with_spacing(10.)
.lens(AppData::left),
)
.horizontal(),
)
.debug_paint_layout()
}
sourcepub fn horizontal(self) -> Self
pub fn horizontal(self) -> Self
Restrict scrolling to the horizontal axis while locking child height.
Examples found in repository?
264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301
fn event_list() -> impl Widget<AppState> {
// Because this would be a HUGE block of repeated code with constants
// we just use a loop to generate the header.
let mut header = Flex::row().with_child(
Label::new(PROPERTIES[0].0)
.fix_width(PROPERTIES[0].1)
.background(HEADER_BACKGROUND),
);
for (name, size) in PROPERTIES.iter().skip(1) {
// Keep in mind that later on, in the main function,
// we set the default spacer values. Without explicitly
// setting them the default spacer is bigger, and is
// probably not desirable for your purposes.
header.add_default_spacer();
header.add_child(
Label::new(*name)
.fix_width(*size)
.background(HEADER_BACKGROUND),
);
}
Scroll::new(
Flex::column()
.cross_axis_alignment(CrossAxisAlignment::Start)
.with_child(header)
.with_default_spacer()
.with_flex_child(
// `List::new` generates a list entry for every element in the `Vec`.
// In this case it shows a log entry for every element in `AppState::events`.
// `make_list_item` generates this new log entry.
Scroll::new(List::new(make_list_item).lens(AppState::events)).vertical(),
1.0,
)
.background(Color::WHITE),
)
.horizontal()
.padding(10.0)
}
More examples
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
fn ui_builder() -> impl Widget<AppData> {
let mut root = Flex::column();
// Build a button to add children to both lists
root.add_child(
Button::new("Add")
.on_click(|_, data: &mut AppData, _| {
// Add child to left list
data.l_index += 1;
data.left.push_back(data.l_index as u32);
// Add child to right list
data.r_index += 1;
data.right.push_back(data.r_index as u32);
})
.fix_height(30.0)
.expand_width(),
);
let mut lists = Flex::row().cross_axis_alignment(CrossAxisAlignment::Start);
// Build a simple list
lists.add_flex_child(
Scroll::new(List::new(|| {
Label::new(|item: &u32, _env: &_| format!("List item #{item}"))
.align_vertical(UnitPoint::LEFT)
.padding(10.0)
.expand()
.height(50.0)
.background(Color::rgb(0.5, 0.5, 0.5))
}))
.vertical()
.lens(AppData::left),
1.0,
);
// Build a list with shared data
lists.add_flex_child(
Scroll::new(
List::new(|| {
Flex::row()
.with_child(
Label::new(|(_, item): &(Vector<u32>, u32), _env: &_| {
format!("List item #{item}")
})
.align_vertical(UnitPoint::LEFT),
)
.with_flex_spacer(1.0)
.with_child(
Button::new("Delete")
.on_click(|_ctx, (shared, item): &mut (Vector<u32>, u32), _env| {
// We have access to both child's data and shared data.
// Remove element from right list.
shared.retain(|v| v != item);
})
.fix_size(80.0, 20.0)
.align_vertical(UnitPoint::CENTER),
)
.padding(10.0)
.background(Color::rgb(0.5, 0.0, 0.5))
.fix_height(50.0)
})
.with_spacing(10.),
)
.vertical()
.lens(lens::Identity.map(
// Expose shared data with children data
|d: &AppData| (d.right.clone(), d.right.clone()),
|d: &mut AppData, x: (Vector<u32>, Vector<u32>)| {
// If shared data was changed reflect the changes in our AppData
d.right = x.0
},
)),
1.0,
);
root.add_flex_child(lists, 1.0);
root.with_child(Label::new("horizontal list"))
.with_child(
Scroll::new(
List::new(|| {
Label::new(|item: &u32, _env: &_| format!("List item #{item}"))
.padding(10.0)
.background(Color::rgb(0.5, 0.5, 0.0))
.fix_height(50.0)
})
.horizontal()
.with_spacing(10.)
.lens(AppData::left),
)
.horizontal(),
)
.debug_paint_layout()
}
sourcepub fn content_must_fill(self, must_fill: bool) -> Self
pub fn content_must_fill(self, must_fill: bool) -> Self
Builder-style method to set whether the child must fill the view.
If false
(the default) there is no minimum constraint on the child’s
size. If true
, the child must have at least the same size as the parent
Scroll
widget.
sourcepub fn disable_scrollbars(self) -> Self
pub fn disable_scrollbars(self) -> Self
Disable both scrollbars
sourcepub fn set_content_must_fill(&mut self, must_fill: bool)
pub fn set_content_must_fill(&mut self, must_fill: bool)
Set whether the child’s size must be greater than or equal the size of
the Scroll
widget.
See content_must_fill
for more details.
sourcepub fn set_enabled_scrollbars(&mut self, enabled: ScrollbarsEnabled)
pub fn set_enabled_scrollbars(&mut self, enabled: ScrollbarsEnabled)
Set which scrollbars should be enabled.
If scrollbars are disabled, scrolling will still occur as a result of scroll events from a trackpad or scroll wheel.
sourcepub fn set_vertical_scroll_enabled(&mut self, enabled: bool)
pub fn set_vertical_scroll_enabled(&mut self, enabled: bool)
Set whether the content can be scrolled in the vertical direction.
sourcepub fn set_horizontal_scroll_enabled(&mut self, enabled: bool)
pub fn set_horizontal_scroll_enabled(&mut self, enabled: bool)
Set whether the content can be scrolled in the horizontal direction.
sourcepub fn child_size(&self) -> Size
pub fn child_size(&self) -> Size
Returns the size of the child widget.
sourcepub fn viewport_rect(&self) -> Rect
pub fn viewport_rect(&self) -> Rect
Returns a Rect
representing the currently visible region.
This is relative to the bounds of the content.
sourcepub fn offset_for_axis(&self, axis: Axis) -> f64
pub fn offset_for_axis(&self, axis: Axis) -> f64
Return the scroll offset on a particular axis