mirror of
https://github.com/storytold/deckcraft.git
synced 2026-10-24 10:37:03 -06:00
Theme: text fields show in the light theme (#1204)
In the light theme a text field had the same white fill as the dialog, pane or ribbon it sits on and no outline (nor a visible focus outline), so only its label or hint showed. Fill text fields with a light grey, as the dark theme already sets them apart with a darker well. The File tab keeps its outlined white fields.
This commit is contained in:
@@ -388,6 +388,7 @@ pub fn show(app: &mut SlideApp, ui: &mut Ui) {
|
||||
});
|
||||
egui::CentralPanel::default().frame(egui::Frame::NONE.fill(page_bg)).show(ui, |ui| {
|
||||
// Fields, boxes and buttons sit on the page's own colour: outline them, as PowerPoint does.
|
||||
ui.visuals_mut().text_edit_bg_color = Some(t.field_bg);
|
||||
let w = &mut ui.visuals_mut().widgets;
|
||||
w.inactive.bg_fill = t.field_bg;
|
||||
w.inactive.weak_bg_fill = t.field_bg;
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
//! Text fields must show where they are: in the light theme the field was as white as the dialog,
|
||||
//! pane or ribbon it sits on and had no outline, so only its label (or hint) was visible (#1204).
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used)]
|
||||
|
||||
use deckcraft_engine::Session;
|
||||
use egui::accesskit::Role;
|
||||
use egui::epaint::Shape;
|
||||
use egui::{Color32, Rect};
|
||||
use egui_kittest::Harness;
|
||||
use egui_kittest::kittest::Queryable;
|
||||
use serde_json::json;
|
||||
|
||||
use crate::{Services, SlideApp, theme};
|
||||
|
||||
fn harness(app: SlideApp) -> Harness<'static, SlideApp> {
|
||||
let mut h = Harness::builder().with_size(egui::vec2(1400.0, 900.0)).with_step_dt(1.0 / 60.0).build_ui_state(
|
||||
|ui, app: &mut SlideApp| {
|
||||
app.logic(ui.ctx());
|
||||
app.ui(ui);
|
||||
},
|
||||
app,
|
||||
);
|
||||
// The app keeps asking for frames, so step instead of run().
|
||||
h.run_steps(3);
|
||||
h
|
||||
}
|
||||
|
||||
/// The largest per-channel difference between two colours.
|
||||
fn distance(a: Color32, b: Color32) -> u8 {
|
||||
a.r().abs_diff(b.r()).max(a.g().abs_diff(b.g())).max(a.b().abs_diff(b.b()))
|
||||
}
|
||||
|
||||
/// The frame egui painted for the text field at `field` (its fill and outline).
|
||||
fn frame_at(h: &Harness<'_, SlideApp>, field: Rect) -> (Color32, egui::Stroke) {
|
||||
h.output()
|
||||
.shapes
|
||||
.iter()
|
||||
.find_map(|c| match &c.shape {
|
||||
Shape::Rect(r) if r.rect.min.distance(field.min) < 2.0 && r.rect.max.distance(field.max) < 2.0 => Some((r.fill, r.stroke)),
|
||||
_ => None,
|
||||
})
|
||||
.unwrap_or_else(|| panic!("no frame painted for the field at {field:?}"))
|
||||
}
|
||||
|
||||
/// The field stands apart from `surface` by its fill or by its outline.
|
||||
fn assert_visible(h: &Harness<'_, SlideApp>, what: &str, surface: Color32) {
|
||||
let fields: Vec<Rect> = h.query_all_by_role(Role::TextInput).map(|n| n.rect()).collect();
|
||||
assert!(!fields.is_empty(), "{what}: no text field");
|
||||
for field in fields {
|
||||
let (fill, stroke) = frame_at(h, field);
|
||||
let outlined = stroke.width >= 1.0 && distance(stroke.color, surface) >= 24;
|
||||
assert!(
|
||||
outlined || distance(fill, surface) >= 16,
|
||||
"{what}: the field at {field:?} (fill {fill:?}, outline {stroke:?}) can't be told from the {surface:?} behind it"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn light_text_fields_show_in_dialogs_panes_and_the_ribbon() {
|
||||
let light = theme::Tokens::light();
|
||||
|
||||
// Home › Find: "Find what:" in the dialog card.
|
||||
let mut h = harness(SlideApp::new(Session::with_new(), Services::default()));
|
||||
h.state_mut().run("app.find", json!({})).unwrap();
|
||||
h.run_steps(3);
|
||||
assert_visible(&h, "Find and Replace", h.ctx.global_style().visuals.window_fill);
|
||||
// Still visible with the keyboard in it.
|
||||
let field = h.get_by_role(Role::TextInput).rect();
|
||||
h.get_by_role(Role::TextInput).click();
|
||||
h.run_steps(2);
|
||||
assert!(h.ctx.memory(|m| m.focused().is_some()), "the click puts the keyboard in the field");
|
||||
let (fill, stroke) = frame_at(&h, field);
|
||||
assert!(distance(fill, light.card) >= 16 || distance(stroke.color, light.card) >= 24, "focused field: fill {fill:?}, outline {stroke:?}");
|
||||
|
||||
// Review › Thesaurus: the search box on the task pane.
|
||||
let mut h = harness(SlideApp::new(Session::with_new(), Services::default()));
|
||||
h.state_mut().ui.pane = Some("thesaurus".into());
|
||||
h.run_steps(3);
|
||||
assert_visible(&h, "Thesaurus pane", light.card);
|
||||
|
||||
// Transitions › Duration: the spinner on the ribbon.
|
||||
let mut app = SlideApp::new(Session::with_new(), Services::default());
|
||||
app.ui.tab = "Transitions".into();
|
||||
let h = harness(app);
|
||||
assert_visible(&h, "Transitions ribbon", light.card);
|
||||
}
|
||||
@@ -2052,6 +2052,8 @@ mod compare_tests;
|
||||
#[cfg(test)]
|
||||
mod eyedropper_tests;
|
||||
#[cfg(test)]
|
||||
mod field_frame_tests;
|
||||
#[cfg(test)]
|
||||
mod huge_deck_tests;
|
||||
#[cfg(test)]
|
||||
mod ime_tests;
|
||||
|
||||
@@ -20,6 +20,8 @@ pub struct Tokens {
|
||||
/// The ribbon card and popovers.
|
||||
pub card: Color32,
|
||||
pub card_border: Color32,
|
||||
/// Text fields. They have no outline, so the fill alone must set them apart from the card.
|
||||
pub text_field: Color32,
|
||||
/// Canvas around the slide.
|
||||
pub canvas: Color32,
|
||||
pub text: Color32,
|
||||
@@ -51,6 +53,7 @@ impl Tokens {
|
||||
chrome: Color32::from_rgb(0xF5, 0xF5, 0xF5),
|
||||
card: Color32::WHITE,
|
||||
card_border: Color32::from_rgb(0xE3, 0xE3, 0xE3),
|
||||
text_field: Color32::from_rgb(0xEB, 0xEB, 0xEB),
|
||||
canvas: Color32::from_rgb(0xF0, 0xF0, 0xF0),
|
||||
text: Color32::from_rgb(0x24, 0x24, 0x24),
|
||||
text_dim: Color32::from_rgb(0x5C, 0x5C, 0x5C),
|
||||
@@ -79,6 +82,7 @@ impl Tokens {
|
||||
chrome: Color32::from_rgb(0x26, 0x26, 0x26),
|
||||
card: Color32::from_rgb(0x33, 0x33, 0x33),
|
||||
card_border: Color32::from_rgb(0x44, 0x44, 0x44),
|
||||
text_field: Color32::from_rgb(0x2A, 0x2A, 0x2A),
|
||||
canvas: Color32::from_rgb(0x1E, 0x1E, 0x1E),
|
||||
text: Color32::from_rgb(0xEE, 0xEE, 0xEE),
|
||||
text_dim: Color32::from_rgb(0xBB, 0xBB, 0xBB),
|
||||
@@ -118,6 +122,7 @@ pub fn apply(ctx: &egui::Context, t: &Tokens) {
|
||||
v.panel_fill = t.chrome;
|
||||
v.window_fill = t.card;
|
||||
v.extreme_bg_color = t.field_bg;
|
||||
v.text_edit_bg_color = Some(t.text_field);
|
||||
v.faint_bg_color = t.hover;
|
||||
v.window_stroke = Stroke::new(1.0, t.card_border);
|
||||
v.window_corner_radius = CornerRadius::same(10);
|
||||
|
||||
Reference in New Issue
Block a user