breadpad: fix reminder alert, snooze popover, and compose popup

Reminder alert:
- Dismiss now matches Snooze's full-opacity text color instead of 0.6
  alpha, which read as a disabled button.
- Button row inset brought to 20px to match the header/body zone above
  it (was 16px, visible as a step across the divider).

Snooze popover:
- Options are left-aligned (each button's label now sets its own
  xalign) and span the full row width instead of centering as a
  ragged-left stack of labels.
- Added a hairline divider between options so the list reads as
  distinct rows even in the popover's static/non-hover state.
- Added a "Custom…" option that reveals a free-form time entry,
  reusing the same rule-based time parsing breadman's dialogs use,
  instead of only offering the three fixed presets.
- Popover now matches the reminder card's flat-bordered elevation
  (no arrow, 1px border, no shadow) instead of GTK's default
  arrow+drop-shadow popover chrome.

Compose popup: dropped the bare accent-teal checkmark submit button
(same colour as the selected type pill, unlabeled) in favor of a
"Press Enter to add" hint, since the popup already grabs focus and is
keyboard-driven. Also made the entry's CSS padding uniform (14px on
all sides, was 12px/16px).
This commit is contained in:
Breadway 2026-07-31 07:21:13 +08:00
parent de7965edae
commit 26cb31354f
2 changed files with 107 additions and 27 deletions

View file

@ -33,7 +33,7 @@ window { border-radius: 8px; }
color: @fg;
border: 2px solid @blue;
border-radius: 6px;
padding: 12px 16px;
padding: 14px;
font-size: 14px;
caret-color: @fg;
}
@ -183,12 +183,15 @@ window { border-radius: 8px; }
color: @fg;
}
/* Dismiss and Snooze are both secondary/outline actions and should read at
equal weight - Dismiss used to sit at 0.6 alpha next to Snooze's full
opacity, which made the button that closes the reminder look disabled. */
.reminder-dismiss {
background: transparent;
border: 1px solid @overlay;
border-radius: 8px;
padding: 8px 16px;
color: alpha(@fg, 0.6);
color: @fg;
}
.reminder-dismiss:hover { background: shade(@bg, 1.1); }
@ -203,15 +206,40 @@ window { border-radius: 8px; }
.reminder-snooze:hover { background: shade(@bg, 1.1); }
/* Left-aligned (the button's child label sets xalign itself), full-width
row with a hairline divider so the list reads as distinct clickable rows
even before hover - a hover tint alone doesn't show up in a static
reading of the popover's default state. */
.snooze-option {
background: transparent;
border: none;
border-radius: 6px;
padding: 8px 12px;
padding: 10px 12px;
color: @fg;
border-bottom: 1px solid alpha(@overlay, 0.15);
}
.snooze-option:hover { background: shade(@bg, 1.2); }
.snooze-custom-entry {
background: @bg;
color: @fg;
border: 1px solid @overlay;
border-radius: 6px;
padding: 8px 12px;
margin: 4px;
}
.snooze-custom-entry:focus-within { border-color: @blue; outline: none; }
/* Matches the reminder alert card's flat-bordered elevation (1px border,
8px radius, no shadow) instead of GTK's default arrow+drop-shadow popover
chrome - the two surfaces used to speak two different elevation
languages. */
popover.snooze-popover > contents {
border: 1px solid @overlay;
box-shadow: none;
}
"#);
if let Some(extra) = user_css {

View file

@ -3,6 +3,7 @@ use breadpad_shared::{
calendar::CalDavClient,
classifier::Classifier,
config::Config,
parser::parse_rule_based,
scheduler::Scheduler,
store::Store,
types::{Note, NoteType},
@ -492,14 +493,15 @@ fn build_reminder_window(
.orientation(gtk4::Orientation::Horizontal)
.build());
// Button row
// Button row — same inset as the header/body zone above (20px), which
// used to be 16px here, visible as a step across the divider.
let btn_row = gtk4::Box::builder()
.orientation(gtk4::Orientation::Horizontal)
.spacing(8)
.margin_top(12)
.margin_bottom(12)
.margin_start(16)
.margin_end(16)
.margin_start(20)
.margin_end(20)
.build();
let dismiss_btn = gtk4::Button::builder()
@ -507,23 +509,35 @@ fn build_reminder_window(
.css_classes(["reminder-dismiss"])
.build();
// Snooze popover
// Snooze popover. No arrow and a matching flat border (see
// popover.snooze-popover in the shared theme) so it reads as the same
// elevation language as the reminder card, instead of GTK's default
// arrow+drop-shadow chrome next to the card's flat 1px border.
let snooze_popover = gtk4::Popover::new();
snooze_popover.set_has_arrow(false);
snooze_popover.add_css_class("snooze-popover");
let snooze_vbox = gtk4::Box::builder()
.orientation(gtk4::Orientation::Vertical)
.spacing(4)
.margin_top(8)
.margin_bottom(8)
.margin_start(8)
.margin_end(8)
.spacing(0)
.margin_top(4)
.margin_bottom(4)
.build();
// Left-aligned row: a bare Button::builder().label() centers its text,
// so each option gets an explicit xalign(0.0) label as its child and
// hexpand(true) so the row fills the popover's full width instead of
// shrinking to the longest label.
let snooze_option_row = |label: &str| {
gtk4::Button::builder()
.child(&gtk4::Label::builder().label(label).xalign(0.0).build())
.css_classes(["snooze-option"])
.hexpand(true)
.build()
};
for opt in &cfg.settings.snooze_options {
let label = humanize_snooze(opt).to_string();
let btn = gtk4::Button::builder()
.label(&label)
.css_classes(["snooze-option"])
.build();
let btn = snooze_option_row(&label);
let key = opt.clone();
let note_c = note.clone();
let cfg_c = cfg.clone();
@ -543,6 +557,47 @@ fn build_reminder_window(
});
snooze_vbox.append(&btn);
}
// Custom… — reuses the same free-form time parsing breadman's dialogs
// already use, rather than inventing a separate time-picker widget.
let custom_entry = gtk4::Entry::builder()
.placeholder_text("tomorrow 9am / in 45 minutes")
.css_classes(["snooze-custom-entry"])
.visible(false)
.build();
{
let note_c = note.clone();
let cfg_c = cfg.clone();
let win_c = window.clone();
let popover_c = snooze_popover.clone();
let entry_c = custom_entry.clone();
custom_entry.connect_activate(move |_| {
let text = entry_c.text().to_string();
let parsed = parse_rule_based(&text, &cfg_c.reminders.default_morning);
if let Some(until) = parsed.time {
if let Ok(store) = Store::new().map(|s| s.with_calendar_if_enabled(&cfg_c)) {
let mut updated = note_c.as_ref().clone();
updated.snoozed_until = Some(until);
let _ = store.update_note(&updated);
let _ = Scheduler::schedule(&updated);
}
popover_c.popdown();
win_c.close();
}
});
}
let custom_btn = snooze_option_row("Custom\u{2026}");
{
let custom_entry_c = custom_entry.clone();
custom_btn.connect_clicked(move |btn| {
btn.set_visible(false);
custom_entry_c.set_visible(true);
custom_entry_c.grab_focus();
});
}
snooze_vbox.append(&custom_btn);
snooze_vbox.append(&custom_entry);
snooze_popover.set_child(Some(&snooze_vbox));
let snooze_btn = gtk4::MenuButton::builder()
@ -735,10 +790,13 @@ fn build_window(
}
}
// Confirm button
let confirm_btn = gtk4::Button::builder()
.label("")
.css_classes(["confirm-button"])
// No submit button — this popup is keyboard-driven (grabs focus on
// open, Escape closes it) and a bare accent-teal checkmark used to sit
// next to the selected-type pill in the same accent teal, carrying two
// different meanings in one colour. A hint is enough.
let enter_hint = gtk4::Label::builder()
.label("Press Enter to add")
.css_classes(["dim-label"])
.build();
let bottom_row = gtk4::Box::builder()
@ -749,7 +807,7 @@ fn build_window(
let spacer = gtk4::Box::builder().hexpand(true).build();
bottom_row.append(&spacer);
bottom_row.append(&confirm_btn);
bottom_row.append(&enter_hint);
vbox.append(&entry);
vbox.append(&bottom_row);
@ -785,12 +843,6 @@ fn build_window(
}
};
// Confirm button click
{
let save = save_and_close.clone();
confirm_btn.connect_clicked(move |_| save());
}
// Entry activate (Enter key)
{
let save = save_and_close.clone();