| 110 |
110 |
|
.show(ui, |ui| add_contents(ui))
|
| 111 |
111 |
|
.inner
|
| 112 |
112 |
|
})
|
| 113 |
|
- |
.and_then(|r| r.inner)
|
|
113 |
+ |
.and_then(bevel_floating)
|
|
114 |
+ |
}
|
|
115 |
+ |
|
|
116 |
+ |
/// Paint a floating surface's raised bevel and hand back its inner value.
|
|
117 |
+ |
///
|
|
118 |
+ |
/// The bevel goes on after `Window::show` has returned, into the window's own
|
|
119 |
+ |
/// layer, so it lands over the frame egui already drew rather than under it.
|
|
120 |
+ |
/// A `Window` builds its frame from `window_stroke`, which is one stroke with
|
|
121 |
+ |
/// no per-side control, so this is the only place the two-tone edge can come
|
|
122 |
+ |
/// from.
|
|
123 |
+ |
///
|
|
124 |
+ |
/// The hard drop shadow is the theme's half of the same job
|
|
125 |
+ |
/// (`visuals.window_shadow`); a floating surface needs both, or it reads as a
|
|
126 |
+ |
/// raised panel that happens to be on top of something.
|
|
127 |
+ |
fn bevel_floating<R>(response: egui::InnerResponse<R>) -> R {
|
|
128 |
+ |
let painter = response
|
|
129 |
+ |
.response
|
|
130 |
+ |
.ctx
|
|
131 |
+ |
.layer_painter(response.response.layer_id);
|
|
132 |
+ |
theme::bevel::paint(
|
|
133 |
+ |
&painter,
|
|
134 |
+ |
response.response.rect,
|
|
135 |
+ |
theme::bevel::Bevel::Raised,
|
|
136 |
+ |
);
|
|
137 |
+ |
response.inner
|
| 114 |
138 |
|
}
|
| 115 |
139 |
|
|
| 116 |
140 |
|
/// Like `modal_window`, but with an optional `open` bool the user can toggle by
|
| 138 |
162 |
|
// per-frame panic in the shared modal scaffold if that ever changes.
|
| 139 |
163 |
|
window
|
| 140 |
164 |
|
.show(ctx, |ui| add_contents(ui))
|
| 141 |
|
- |
.and_then(|r| r.inner)
|
|
165 |
+ |
.and_then(bevel_floating)
|
| 142 |
166 |
|
}
|
| 143 |
167 |
|
|
| 144 |
168 |
|
/// Render a `[Cancel] [primary]` action row at the bottom of a modal.
|
| 166 |
190 |
|
let confirmed = if danger {
|
| 167 |
191 |
|
danger_button_enabled(ui, confirm_label, can_confirm).clicked()
|
| 168 |
192 |
|
} else {
|
| 169 |
|
- |
// Not `primary_button`: that one has no disabled variant, and
|
| 170 |
|
- |
// adding one is a weight change to every modal, which is a
|
| 171 |
|
- |
// separate decision from the shape.
|
| 172 |
|
- |
ui.add_enabled(
|
| 173 |
|
- |
can_confirm,
|
| 174 |
|
- |
egui::Button::new(confirm_label).corner_radius(theme::radius_control()),
|
|
193 |
+ |
// Secondary, not primary: the weight of the affirmative button in
|
|
194 |
+ |
// a modal is a separate decision from its shape, and this row has
|
|
195 |
+ |
// always drawn it at default weight.
|
|
196 |
+ |
bevel_button(
|
|
197 |
+ |
ui,
|
|
198 |
+ |
confirm_label,
|
|
199 |
+ |
ButtonStyle::secondary().enabled(can_confirm),
|
| 175 |
200 |
|
)
|
| 176 |
201 |
|
.clicked()
|
| 177 |
202 |
|
};
|
| 253 |
278 |
|
}
|
| 254 |
279 |
|
ui.add_space(theme::space::peer());
|
| 255 |
280 |
|
ui.horizontal(|ui| {
|
| 256 |
|
- |
if ui.button("Cancel").clicked() {
|
|
281 |
+ |
if secondary_button(ui, "Cancel").clicked() {
|
| 257 |
282 |
|
outcome = NameModalOutcome::Cancelled;
|
| 258 |
283 |
|
}
|
| 259 |
|
- |
if ui.button(submit_label).clicked() {
|
|
284 |
+ |
if secondary_button(ui, submit_label).clicked() {
|
| 260 |
285 |
|
outcome = NameModalOutcome::Submitted(input.trim().to_string());
|
| 261 |
286 |
|
}
|
| 262 |
287 |
|
});
|
| 359 |
384 |
|
|
| 360 |
385 |
|
// --- Toolbar toggle and segmented pills --------------------------------------
|
| 361 |
386 |
|
|
| 362 |
|
- |
/// Toolbar toggle button.
|
|
387 |
+ |
/// Toolbar toggle: a button that stays in.
|
| 363 |
388 |
|
///
|
| 364 |
|
- |
/// Active state colours the label `action`; inactive state colours it
|
| 365 |
|
- |
/// `content_muted`. Returns true on click. Optional `count` renders a parenthesised
|
| 366 |
|
- |
/// suffix (e.g. "Filters (3)") for active-with-count toolbar buttons.
|
|
389 |
+ |
/// Active is `Inset` with a `surface_sunken` fill and an `action` label;
|
|
390 |
+ |
/// inactive is `Raised` with `content_secondary`. The shape carries the state
|
|
391 |
+ |
/// and the colour agrees with it, rather than the colour carrying it alone —
|
|
392 |
+ |
/// the toolbar is where "which of these is on" has to be readable without
|
|
393 |
+ |
/// stopping to look, and a held-in button says that at a glance.
|
|
394 |
+ |
///
|
|
395 |
+ |
/// Returns true on click. Optional `count` renders a parenthesised suffix
|
|
396 |
+ |
/// (e.g. "Filters (3)").
|
| 367 |
397 |
|
pub fn toolbar_toggle(
|
| 368 |
398 |
|
ui: &mut egui::Ui,
|
| 369 |
399 |
|
label: &str,
|
| 375 |
405 |
|
Some(n) if n > 0 => format!("{label} ({n})"),
|
| 376 |
406 |
|
_ => label.to_string(),
|
| 377 |
407 |
|
};
|
| 378 |
|
- |
let colour = if active {
|
| 379 |
|
- |
theme::action()
|
|
408 |
+ |
let (fill, colour) = if active {
|
|
409 |
+ |
(theme::surface_sunken(), theme::action())
|
| 380 |
410 |
|
} else {
|
| 381 |
|
- |
theme::content_muted()
|
|
411 |
+ |
(theme::surface_raised(), theme::content_secondary())
|
| 382 |
412 |
|
};
|
| 383 |
|
- |
ui.button(egui::RichText::new(text).color(colour))
|
| 384 |
|
- |
.on_hover_text(tooltip)
|
| 385 |
|
- |
.clicked()
|
|
413 |
+ |
|
|
414 |
+ |
let response = ui.add(
|
|
415 |
+ |
egui::Button::new(egui::RichText::new(text).color(colour))
|
|
416 |
+ |
.corner_radius(theme::radius_control())
|
|
417 |
+ |
.fill(fill),
|
|
418 |
+ |
);
|
|
419 |
+ |
|
|
420 |
+ |
// Held in because it is on, or held in because a finger is on it. One
|
|
421 |
+ |
// shape, two reasons, which is the whole economy of the idiom.
|
|
422 |
+ |
let kind = if active {
|
|
423 |
+ |
theme::bevel::Bevel::Inset
|
|
424 |
+ |
} else {
|
|
425 |
+ |
press_bevel(&response)
|
|
426 |
+ |
};
|
|
427 |
+ |
theme::bevel::paint(ui.painter(), response.rect, kind);
|
|
428 |
+ |
|
|
429 |
+ |
response.on_hover_text(tooltip).clicked()
|
| 386 |
430 |
|
}
|
| 387 |
431 |
|
|
| 388 |
432 |
|
/// Mutually-exclusive segmented control.
|
| 425 |
469 |
|
// The selected segment is held in, and a segment under a held
|
| 426 |
470 |
|
// pointer shows the same thing. One rule, two reasons to be inset:
|
| 427 |
471 |
|
// this is what the light model buys over a colour swap.
|
| 428 |
|
- |
let kind = if is_active || resp.is_pointer_button_down_on() {
|
|
472 |
+ |
let kind = if is_active {
|
| 429 |
473 |
|
theme::bevel::Bevel::Inset
|
| 430 |
474 |
|
} else {
|
| 431 |
|
- |
theme::bevel::Bevel::Raised
|
|
475 |
+ |
press_bevel(&resp)
|
| 432 |
476 |
|
};
|
| 433 |
477 |
|
theme::bevel::paint(ui.painter(), resp.rect, kind);
|
| 434 |
478 |
|
|
| 450 |
494 |
|
// `confirm_action_row` (above) composes these for the standard modal pattern;
|
| 451 |
495 |
|
// reach for these directly only when building a non-modal action row.
|
| 452 |
496 |
|
//
|
| 453 |
|
- |
// All five ask for `radius_control` by name. Nothing else in the app does:
|
| 454 |
|
- |
// square is the default now (see `apply_theme`), and a rounded corner is this
|
| 455 |
|
- |
// codebase saying "this is a push button". A raw `ui.button` therefore comes
|
| 456 |
|
- |
// out square, which is correct for the toolbar and wrong for a dialog action
|
| 457 |
|
- |
// row -- reach for these rather than for `ui.button` when it is the latter.
|
|
497 |
+ |
// All five are [`bevel_button`] with a style on it, so all five invert when
|
|
498 |
+ |
// held and no caller changed a line to get that. They ask for `radius_control`
|
|
499 |
+ |
// by name; nothing else in the app does, because square is the default now (see
|
|
500 |
+ |
// `apply_theme`) and a rounded corner is this codebase saying "push button".
|
|
501 |
+ |
|
|
502 |
+ |
/// Where a button sits in the action hierarchy.
|
|
503 |
+ |
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
504 |
+ |
pub enum ButtonWeight {
|
|
505 |
+ |
/// The one affirmative action in a row. Strong label.
|
|
506 |
+ |
Primary,
|
|
507 |
+ |
/// Peer actions and Cancel. Default weight.
|
|
508 |
+ |
Secondary,
|
|
509 |
+ |
/// Destructive. Label in `danger` so the consequence is read first.
|
|
510 |
+ |
Danger,
|
|
511 |
+ |
}
|
|
512 |
+ |
|
|
513 |
+ |
/// Everything about a bevelled button that is not its label.
|
|
514 |
+ |
#[derive(Debug, Clone, Copy)]
|
|
515 |
+ |
pub struct ButtonStyle {
|
|
516 |
+ |
pub weight: ButtonWeight,
|
|
517 |
+ |
/// Tighter padding, for per-row affordances and context menus.
|
|
518 |
+ |
pub small: bool,
|
|
519 |
+ |
pub enabled: bool,
|
|
520 |
+ |
}
|
|
521 |
+ |
|
|
522 |
+ |
impl ButtonStyle {
|
|
523 |
+ |
/// The one affirmative action in a row.
|
|
524 |
+ |
pub fn primary() -> Self {
|
|
525 |
+ |
Self {
|
|
526 |
+ |
weight: ButtonWeight::Primary,
|
|
527 |
+ |
small: false,
|
|
528 |
+ |
enabled: true,
|
|
529 |
+ |
}
|
|
530 |
+ |
}
|
|
531 |
+ |
|
|
532 |
+ |
/// Cancel and peer actions.
|
|
533 |
+ |
pub fn secondary() -> Self {
|
|
534 |
+ |
Self {
|
|
535 |
+ |
weight: ButtonWeight::Secondary,
|
|
536 |
+ |
..Self::primary()
|
|
537 |
+ |
}
|
|
538 |
+ |
}
|
|
539 |
+ |
|
|
540 |
+ |
/// Delete, Purge, Discard.
|
|
541 |
+ |
pub fn danger() -> Self {
|
|
542 |
+ |
Self {
|
|
543 |
+ |
weight: ButtonWeight::Danger,
|
|
544 |
+ |
..Self::primary()
|
|
545 |
+ |
}
|
|
546 |
+ |
}
|
|
547 |
+ |
|
|
548 |
+ |
/// Tighter padding.
|
|
549 |
+ |
#[must_use]
|
|
550 |
+ |
pub fn small(mut self) -> Self {
|
|
551 |
+ |
self.small = true;
|
|
552 |
+ |
self
|
|
553 |
+ |
}
|
|
554 |
+ |
|
|
555 |
+ |
/// Greyed and unclickable when `false`.
|
|
556 |
+ |
#[must_use]
|
|
557 |
+ |
pub fn enabled(mut self, enabled: bool) -> Self {
|
|
558 |
+ |
self.enabled = enabled;
|
|
559 |
+ |
self
|
|
560 |
+ |
}
|
|
561 |
+ |
}
|
|
562 |
+ |
|
|
563 |
+ |
/// The button primitive: raised at rest, inset while held.
|
|
564 |
+ |
///
|
|
565 |
+ |
/// The inversion is why this is hand-painted rather than themed. egui expresses
|
|
566 |
+ |
/// a pressed button as a different fill, which reads as "a slightly darker
|
|
567 |
+ |
/// button"; Platinum expresses it as the same object pushed in, which reads as
|
|
568 |
+ |
/// a button being pressed. `Visuals` has no way to say the second, so the frame
|
|
569 |
+ |
/// is painted here from the live response.
|
|
570 |
+ |
///
|
|
571 |
+ |
/// Disabled keeps its raised bevel and drops the label to `content_muted()`. A
|
|
572 |
+ |
/// disabled control that loses its shape stops looking like a control at all,
|
|
573 |
+ |
/// and the user needs to know a button is there before they can wonder why it
|
|
574 |
+ |
/// is off.
|
|
575 |
+ |
///
|
|
576 |
+ |
/// Prefer the named helpers below. Reaching for this directly is a sign the
|
|
577 |
+ |
/// style you want should have a name.
|
|
578 |
+ |
pub fn bevel_button(ui: &mut egui::Ui, label: &str, style: ButtonStyle) -> egui::Response {
|
|
579 |
+ |
let color = match (style.enabled, style.weight) {
|
|
580 |
+ |
(false, _) => theme::content_muted(),
|
|
581 |
+ |
(true, ButtonWeight::Danger) => theme::danger(),
|
|
582 |
+ |
(true, _) => theme::content(),
|
|
583 |
+ |
};
|
|
584 |
+ |
let mut text = egui::RichText::new(label).color(color);
|
|
585 |
+ |
if style.weight == ButtonWeight::Primary {
|
|
586 |
+ |
text = text.strong();
|
|
587 |
+ |
}
|
|
588 |
+ |
|
|
589 |
+ |
let mut button = egui::Button::new(text).corner_radius(theme::radius_control());
|
|
590 |
+ |
if style.small {
|
|
591 |
+ |
button = button.small();
|
|
592 |
+ |
}
|
|
593 |
+ |
|
|
594 |
+ |
let response = ui.add_enabled(style.enabled, button);
|
|
595 |
+ |
theme::bevel::paint(ui.painter(), response.rect, press_bevel(&response));
|
|
596 |
+ |
response
|
|
597 |
+ |
}
|
|
598 |
+ |
|
|
599 |
+ |
/// `Inset` while the pointer is held on this widget, `Raised` otherwise.
|
|
600 |
+ |
///
|
|
601 |
+ |
/// The one rule every interactive primitive in this file shares, so it is
|
|
602 |
+ |
/// written once. Hover and keyboard focus deliberately do not invert: a bevel
|
|
603 |
+ |
/// that moved on hover would say the control had been pressed when it had not.
|
|
604 |
+ |
fn press_bevel(response: &egui::Response) -> theme::bevel::Bevel {
|
|
605 |
+ |
if response.is_pointer_button_down_on() {
|
|
606 |
+ |
theme::bevel::Bevel::Inset
|
|
607 |
+ |
} else {
|
|
608 |
+ |
theme::bevel::Bevel::Raised
|
|
609 |
+ |
}
|
|
610 |
+ |
}
|
| 458 |
611 |
|
|
| 459 |
612 |
|
/// Primary action button. Strong label weight.
|
| 460 |
613 |
|
pub fn primary_button(ui: &mut egui::Ui, label: &str) -> egui::Response {
|
| 461 |
|
- |
ui.add(
|
| 462 |
|
- |
egui::Button::new(egui::RichText::new(label).strong())
|
| 463 |
|
- |
.corner_radius(theme::radius_control()),
|
| 464 |
|
- |
)
|
|
614 |
+ |
bevel_button(ui, label, ButtonStyle::primary())
|
| 465 |
615 |
|
}
|
| 466 |
616 |
|
|
| 467 |
617 |
|
/// Secondary / peer action button. Default weight. Use for Cancel and for any
|
| 468 |
618 |
|
/// action that isn't the primary focus of the row.
|
| 469 |
619 |
|
pub fn secondary_button(ui: &mut egui::Ui, label: &str) -> egui::Response {
|
| 470 |
|
- |
ui.add(egui::Button::new(label).corner_radius(theme::radius_control()))
|
|
620 |
+ |
bevel_button(ui, label, ButtonStyle::secondary())
|
| 471 |
621 |
|
}
|
| 472 |
622 |
|
|
| 473 |
623 |
|
/// Destructive primary action. Label rendered in `danger` so the user
|
| 474 |
624 |
|
/// reads the consequence before clicking. Used for Delete, Purge, Discard.
|
| 475 |
625 |
|
pub fn danger_button(ui: &mut egui::Ui, label: &str) -> egui::Response {
|
| 476 |
|
- |
ui.add(
|
| 477 |
|
- |
egui::Button::new(egui::RichText::new(label).color(theme::danger()))
|
| 478 |
|
- |
.corner_radius(theme::radius_control()),
|
| 479 |
|
- |
)
|
|
626 |
+ |
bevel_button(ui, label, ButtonStyle::danger())
|
| 480 |
627 |
|
}
|
| 481 |
628 |
|
|
| 482 |
629 |
|
/// Destructive action that may be disabled (e.g. Delete-vault when only one
|
| 483 |
|
- |
/// vault remains). Same red colouring as [`danger_button`]; routes through
|
| 484 |
|
- |
/// `add_enabled` so disabled state and hover text work consistently.
|
|
630 |
+ |
/// vault remains). Same red colouring as [`danger_button`] when live, and
|
|
631 |
+ |
/// `content_muted` when not.
|
| 485 |
632 |
|
pub fn danger_button_enabled(ui: &mut egui::Ui, label: &str, enabled: bool) -> egui::Response {
|
| 486 |
|
- |
ui.add_enabled(
|
| 487 |
|
- |
enabled,
|
| 488 |
|
- |
egui::Button::new(egui::RichText::new(label).color(theme::danger()))
|
| 489 |
|
- |
.corner_radius(theme::radius_control()),
|
| 490 |
|
- |
)
|
|
633 |
+ |
bevel_button(ui, label, ButtonStyle::danger().enabled(enabled))
|
| 491 |
634 |
|
}
|
| 492 |
635 |
|
|
| 493 |
636 |
|
/// Small destructive action (per-row Remove/Delete affordances, context-menu
|
| 494 |
637 |
|
/// items inside a tighter layout). Same colouring as [`danger_button`].
|
| 495 |
638 |
|
pub fn danger_small_button(ui: &mut egui::Ui, label: &str) -> egui::Response {
|
| 496 |
|
- |
ui.add(
|
| 497 |
|
- |
egui::Button::new(egui::RichText::new(label).color(theme::danger()))
|
| 498 |
|
- |
.small()
|
| 499 |
|
- |
.corner_radius(theme::radius_control()),
|
| 500 |
|
- |
)
|
|
639 |
+ |
bevel_button(ui, label, ButtonStyle::danger().small())
|
| 501 |
640 |
|
}
|
| 502 |
641 |
|
|
| 503 |
642 |
|
// --- Section headers ---------------------------------------------------------
|
| 545 |
684 |
|
});
|
| 546 |
685 |
|
}
|
| 547 |
686 |
|
|
|
687 |
+ |
// --- Text fields -------------------------------------------------------------
|
|
688 |
+ |
|
|
689 |
+ |
/// Add a `TextEdit` as an inset well.
|
|
690 |
+ |
///
|
|
691 |
+ |
/// **Every `TextEdit` in the app goes through here.** A well is the one shape
|
|
692 |
+ |
/// in Platinum that means "put something here", and it only means that if it is
|
|
693 |
+ |
/// the same well every time: a bevel on seventeen of eighteen fields is worse
|
|
694 |
+ |
/// than a bevel on none, because then the odd one out is saying something the
|
|
695 |
+ |
/// author did not intend.
|
|
696 |
+ |
///
|
|
697 |
+ |
/// The bevel is painted *after* `ui.add`, so the widget's own background lands
|
|
698 |
+ |
/// first and the 1px frame draws on top of it. That ordering is why
|
|
699 |
+ |
/// [`theme::bevel::paint`] fills nothing.
|
|
700 |
+ |
pub fn text_field(ui: &mut egui::Ui, edit: egui::TextEdit<'_>) -> egui::Response {
|
|
701 |
+ |
let response = ui.add(edit);
|
|
702 |
+ |
theme::bevel::paint(ui.painter(), response.rect, theme::bevel::Bevel::Inset);
|
|
703 |
+ |
response
|
|
704 |
+ |
}
|
|
705 |
+ |
|
| 548 |
706 |
|
// --- Selectable rows ---------------------------------------------------------
|
| 549 |
707 |
|
|
| 550 |
708 |
|
/// Render a selectable label that truncates with an ellipsis instead of
|
| 704 |
862 |
|
};
|
| 705 |
863 |
|
ui.painter()
|
| 706 |
864 |
|
.rect_filled(rect, theme::radius_container(), bg);
|
| 707 |
|
- |
theme::bevel::paint(ui.painter(), rect, theme::bevel::Bevel::Raised);
|
|
865 |
+ |
// A chip answers a click, so it inverts like everything else that does.
|
|
866 |
+ |
theme::bevel::paint(ui.painter(), rect, press_bevel(&response));
|
| 708 |
867 |
|
ui.painter().text(
|
| 709 |
868 |
|
rect.center(),
|
| 710 |
869 |
|
egui::Align2::CENTER_CENTER,
|
| 737 |
896 |
|
} else {
|
| 738 |
897 |
|
theme::danger()
|
| 739 |
898 |
|
};
|
|
899 |
+ |
// Not `danger_small_button`: the dimmed-until-hovered colour is this
|
|
900 |
+ |
// widget's own affordance decision and does not belong in the button
|
|
901 |
+ |
// vocabulary. The bevel rule still applies, so it is applied here.
|
| 740 |
902 |
|
let btn = ui
|
| 741 |
903 |
|
.add(egui::Button::new(egui::RichText::new("x").small().color(x_color)).small())
|
| 742 |
904 |
|
.on_hover_text("Remove tag");
|
|
905 |
+ |
theme::bevel::paint(ui.painter(), btn.rect, press_bevel(&btn));
|
| 743 |
906 |
|
if btn.clicked() {
|
| 744 |
907 |
|
removed = true;
|
| 745 |
908 |
|
}
|
| 773 |
936 |
|
mod tests {
|
| 774 |
937 |
|
use super::*;
|
| 775 |
938 |
|
|
|
939 |
+ |
// Button styles
|
|
940 |
+ |
//
|
|
941 |
+ |
// The styles are what the vocabulary is made of, and each named helper is
|
|
942 |
+ |
// one of them. Painting needs a live render context and is not testable
|
|
943 |
+ |
// here; which style each helper picks is, and that is the part that would
|
|
944 |
+ |
// silently go wrong in a refactor.
|
|
945 |
+ |
|
|
946 |
+ |
#[test]
|
|
947 |
+ |
fn the_named_helpers_cover_the_weights() {
|
|
948 |
+ |
assert_eq!(ButtonStyle::primary().weight, ButtonWeight::Primary);
|
|
949 |
+ |
assert_eq!(ButtonStyle::secondary().weight, ButtonWeight::Secondary);
|
|
950 |
+ |
assert_eq!(ButtonStyle::danger().weight, ButtonWeight::Danger);
|
|
951 |
+ |
}
|
|
952 |
+ |
|
|
953 |
+ |
#[test]
|
|
954 |
+ |
fn styles_default_to_enabled_and_full_size() {
|
|
955 |
+ |
for style in [
|
|
956 |
+ |
ButtonStyle::primary(),
|
|
957 |
+ |
ButtonStyle::secondary(),
|
|
958 |
+ |
ButtonStyle::danger(),
|
|
959 |
+ |
] {
|
|
960 |
+ |
assert!(style.enabled, "a button is live unless it says otherwise");
|
|
961 |
+ |
assert!(!style.small);
|
|
962 |
+ |
}
|
|
963 |
+ |
}
|
|
964 |
+ |
|
|
965 |
+ |
#[test]
|
|
966 |
+ |
fn the_modifiers_compose_and_keep_the_weight() {
|
|
967 |
+ |
let style = ButtonStyle::danger().small().enabled(false);
|
|
968 |
+ |
assert_eq!(style.weight, ButtonWeight::Danger);
|
|
969 |
+ |
assert!(style.small);
|
|
970 |
+ |
assert!(!style.enabled);
|
|
971 |
+ |
}
|
|
972 |
+ |
|
| 776 |
973 |
|
// format_bytes
|
| 777 |
974 |
|
|
| 778 |
975 |
|
#[test]
|