1
0
mirror of https://github.com/emilk/egui.git synced 2026-08-29 04:40:03 -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, format: String,
highlight_weekends: bool, highlight_weekends: bool,
start_end_years: Option<RangeInclusive<i32>>, start_end_years: Option<RangeInclusive<i32>>,
reverse_years: bool,
year_scroll_to: Option<i32>,
} }
impl<'a> DatePickerButton<'a> { impl<'a> DatePickerButton<'a> {
@@ -36,6 +38,8 @@ impl<'a> DatePickerButton<'a> {
format: "%Y-%m-%d".to_owned(), format: "%Y-%m-%d".to_owned(),
highlight_weekends: true, highlight_weekends: true,
start_end_years: None, 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.start_end_years = Some(start_end_years);
self 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<'_> { impl Widget for DatePickerButton<'_> {
@@ -154,7 +173,6 @@ impl Widget for DatePickerButton<'_> {
pos.x = button_response.rect.right() - width_with_padding; 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()); pos.x = pos.x.max(ui.style().spacing.window_margin.leftf());
//TODO(elwerene): Better positioning //TODO(elwerene): Better positioning
@@ -182,6 +200,8 @@ impl Widget for DatePickerButton<'_> {
calendar_week: self.calendar_week, calendar_week: self.calendar_week,
highlight_weekends: self.highlight_weekends, highlight_weekends: self.highlight_weekends,
start_end_years: self.start_end_years, start_end_years: self.start_end_years,
reverse_years: self.reverse_years,
year_scroll_to: self.year_scroll_to,
} }
.draw(ui) .draw(ui)
}) })

View File

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