1
0
mirror of https://github.com/emilk/egui.git synced 2026-06-26 22:53:14 -04:00

Add DatePickerButton::reverse_years/year_scroll_to (#7978)

## What

Two new builder methods on `DatePickerButton`:

- **`reverse_years(bool)`** — lists years in descending order (newest
first). Useful when users are more likely to pick recent years.
- **`year_scroll_to(i32)`** — scrolls the year dropdown to a specific
year when it first opens, centred in the list. Defaults to the currently
selected year, so the picker no longer opens at the top of a 110-item
list.

## Why

The year `ComboBox` currently always renders in ascending order and
opens scrolled to the top. For a range spanning e.g. 1925–2035, the
current year is buried near the bottom. Users have to scroll past ~100
entries every time they open the picker.

## Notes

- Both options are purely additive builder methods — no breaking
changes.
- `year_scroll_needed` is a persisted state flag that is set on popup
open and cleared after the first scroll, so the user can freely scroll
the list after that.
- Existing behaviour is unchanged when neither method is called.

Co-authored-by: Simon Deurell <simon@deurell.se>
This commit is contained in:
Deuracell
2026-03-24 12:50:20 +00:00
committed by GitHub
parent d985bf9b83
commit 3a2d437bd7
2 changed files with 49 additions and 11 deletions

View File

@@ -21,6 +21,8 @@ pub struct DatePickerButton<'a> {
format: String,
highlight_weekends: bool,
start_end_years: Option<RangeInclusive<i32>>,
reverse_years: bool,
year_scroll_to: Option<i32>,
}
impl<'a> DatePickerButton<'a> {
@@ -36,6 +38,8 @@ impl<'a> DatePickerButton<'a> {
format: "%Y-%m-%d".to_owned(),
highlight_weekends: true,
start_end_years: None,
reverse_years: false,
year_scroll_to: None,
}
}
@@ -115,6 +119,21 @@ impl<'a> DatePickerButton<'a> {
self.start_end_years = Some(start_end_years);
self
}
/// List years in descending order in the year dropdown. (Default: false)
#[inline]
pub fn reverse_years(mut self, reverse_years: bool) -> Self {
self.reverse_years = reverse_years;
self
}
/// Scroll the year dropdown to this year when the picker first opens.
/// Defaults to the currently selected year.
#[inline]
pub fn year_scroll_to(mut self, year: i32) -> Self {
self.year_scroll_to = Some(year);
self
}
}
impl Widget for DatePickerButton<'_> {
@@ -154,7 +173,6 @@ impl Widget for DatePickerButton<'_> {
pos.x = button_response.rect.right() - width_with_padding;
}
// Check to make sure the calendar never is displayed out of window
pos.x = pos.x.max(ui.style().spacing.window_margin.leftf());
//TODO(elwerene): Better positioning
@@ -182,6 +200,8 @@ impl Widget for DatePickerButton<'_> {
calendar_week: self.calendar_week,
highlight_weekends: self.highlight_weekends,
start_end_years: self.start_end_years,
reverse_years: self.reverse_years,
year_scroll_to: self.year_scroll_to,
}
.draw(ui)
})

View File

@@ -13,6 +13,7 @@ struct DatePickerPopupState {
month: u32,
day: u32,
setup: bool,
year_scroll_needed: bool,
}
impl DatePickerPopupState {
@@ -36,6 +37,8 @@ pub(crate) struct DatePickerPopup<'a> {
pub calendar_week: bool,
pub highlight_weekends: bool,
pub start_end_years: Option<std::ops::RangeInclusive<i32>>,
pub reverse_years: bool,
pub year_scroll_to: Option<i32>,
}
impl DatePickerPopup<'_> {
@@ -51,6 +54,7 @@ impl DatePickerPopup<'_> {
popup_state.month = self.selection.month();
popup_state.day = self.selection.day();
popup_state.setup = true;
popup_state.year_scroll_needed = true;
ui.data_mut(|data| data.insert_persisted(id, popup_state.clone()));
}
@@ -60,7 +64,7 @@ impl DatePickerPopup<'_> {
let spacing = 2.0;
ui.spacing_mut().item_spacing = Vec2::splat(spacing);
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend); // Don't wrap any text
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
StripBuilder::new(ui)
.clip(false)
@@ -89,15 +93,30 @@ impl DatePickerPopup<'_> {
Some(range) => (*range.start(), *range.end()),
None => (today.year() - 100, today.year() + 10),
};
for year in start_year..=end_year {
if ui
.selectable_value(
&mut popup_state.year,
year,
year.to_string(),
)
.changed()
let scroll_to_year =
self.year_scroll_to.unwrap_or(popup_state.year);
let years: Vec<i32> = if self.reverse_years {
(start_year..=end_year).rev().collect()
} else {
(start_year..=end_year).collect()
};
for year in years {
let resp = ui.selectable_value(
&mut popup_state.year,
year,
year.to_string(),
);
if popup_state.year_scroll_needed
&& year == scroll_to_year
{
resp.scroll_to_me(Some(Align::Center));
popup_state.year_scroll_needed = false;
ui.memory_mut(|mem| {
mem.data
.insert_persisted(id, popup_state.clone());
});
}
if resp.changed() {
popup_state.day = popup_state
.day
.min(popup_state.last_day_of_month());
@@ -349,7 +368,6 @@ impl DatePickerPopup<'_> {
);
if day == today {
// Encircle today's date
let stroke = ui
.visuals()
.widgets