| 77 |
77 |
|
std::fs::write(path, makeover_webview::stylesheet(opts)).expect("write layout css");
|
| 78 |
78 |
|
}
|
| 79 |
79 |
|
|
| 80 |
|
- |
/// Both of the above at the layout every Tauri consumer already uses:
|
| 81 |
|
- |
/// `themes/` beside the manifest, and `frontend/css/layout.css` under it.
|
|
80 |
+ |
/// Write `makeover-geometry`'s spacing layer to `path`, with the canonical
|
|
81 |
+ |
/// density selection.
|
|
82 |
+ |
///
|
|
83 |
+ |
/// **Density is a capability, not a device and not a width.** A narrow window
|
|
84 |
+ |
/// on a desktop still has a pointer in it and a tablet at full width still has
|
|
85 |
+ |
/// a finger, so the touch preset hangs off `(hover: none), (pointer: coarse)`
|
|
86 |
+ |
/// rather than off a breakpoint or a user-agent string. That is the question
|
|
87 |
+ |
/// the platform actually answers.
|
|
88 |
+ |
///
|
|
89 |
+ |
/// `explicit_touch` names a selector an app sets when the *user* has chosen,
|
|
90 |
+ |
/// which is emitted last and therefore wins at equal specificity. Detection is
|
|
91 |
+ |
/// a default, not a verdict: a touchscreen laptop and someone who simply wants
|
|
92 |
+ |
/// roomier targets are both real and neither is visible to a media query.
|
|
93 |
+ |
///
|
|
94 |
+ |
/// This settles a policy the three consumers previously answered three ways:
|
|
95 |
+ |
/// GoingsOn sniffed the user agent behind a class, Balanced Breakfast used
|
|
96 |
+ |
/// `(hover: none)` alone, and audiofiles had no switch at all. It lives here
|
|
97 |
+ |
/// rather than in `makeover-geometry` only because that crate is published at
|
|
98 |
+ |
/// 0.1.0 and five consumers take it from the registry; it belongs there at its
|
|
99 |
+ |
/// next release.
|
|
100 |
+ |
///
|
|
101 |
+ |
/// # Panics
|
|
102 |
+ |
///
|
|
103 |
+ |
/// If the file cannot be written.
|
|
104 |
+ |
pub fn geometry_css(path: impl AsRef<Path>, explicit_touch: Option<&str>) {
|
|
105 |
+ |
use makeover_geometry::{Density, gap_css_overrides, geometry_css_vars};
|
|
106 |
+ |
|
|
107 |
+ |
let mut css = String::from(
|
|
108 |
+ |
"/* Generated by makeover-build from makeover-geometry. Do not edit.\n \
|
|
109 |
+ |
Spacing is named by relationship, not by size. Touch density is a\n \
|
|
110 |
+ |
capability question: a narrow desktop window still has a pointer, a\n \
|
|
111 |
+ |
full-width tablet still has a finger. */\n",
|
|
112 |
+ |
);
|
|
113 |
+ |
css.push_str(&geometry_css_vars(Density::Pointer));
|
|
114 |
+ |
css.push_str("\n@media (hover: none), (pointer: coarse) {\n");
|
|
115 |
+ |
for line in gap_css_overrides(":root", Density::Touch).lines() {
|
|
116 |
+ |
css.push_str(" ");
|
|
117 |
+ |
css.push_str(line);
|
|
118 |
+ |
css.push('\n');
|
|
119 |
+ |
}
|
|
120 |
+ |
css.push_str("}\n");
|
|
121 |
+ |
if let Some(selector) = explicit_touch {
|
|
122 |
+ |
css.push_str("\n/* An explicit user choice, last so it wins over detection. */\n");
|
|
123 |
+ |
css.push_str(&gap_css_overrides(selector, Density::Touch));
|
|
124 |
+ |
}
|
|
125 |
+ |
|
|
126 |
+ |
std::fs::write(path, css).expect("write geometry css");
|
|
127 |
+ |
}
|
|
128 |
+ |
|
|
129 |
+ |
/// All three generated files at the layout every Tauri consumer already uses:
|
|
130 |
+ |
/// `themes/` beside the manifest, and `frontend/css/{geometry,layout}.css`
|
|
131 |
+ |
/// under it.
|
| 82 |
132 |
|
///
|
| 83 |
133 |
|
/// Pass `env!("CARGO_MANIFEST_DIR")`. Consumers that want different paths call
|
| 84 |
134 |
|
/// [`themes`] and [`layout_css`] directly.
|
| 86 |
136 |
|
/// # Panics
|
| 87 |
137 |
|
///
|
| 88 |
138 |
|
/// If either file cannot be written.
|
| 89 |
|
- |
pub fn tauri_frontend(manifest_dir: impl AsRef<Path>, opts: &makeover_webview::Emit) {
|
|
139 |
+ |
pub fn tauri_frontend(
|
|
140 |
+ |
manifest_dir: impl AsRef<Path>,
|
|
141 |
+ |
opts: &makeover_webview::Emit,
|
|
142 |
+ |
explicit_touch: Option<&str>,
|
|
143 |
+ |
) {
|
| 90 |
144 |
|
let root = manifest_dir.as_ref();
|
|
145 |
+ |
let css = root.join("frontend").join("css");
|
| 91 |
146 |
|
themes(root.join("themes"));
|
| 92 |
|
- |
layout_css(root.join("frontend").join("css").join("layout.css"), opts);
|
|
147 |
+ |
geometry_css(css.join("geometry.css"), explicit_touch);
|
|
148 |
+ |
layout_css(css.join("layout.css"), opts);
|
| 93 |
149 |
|
}
|
| 94 |
150 |
|
|
| 95 |
151 |
|
#[cfg(test)]
|
| 149 |
205 |
|
}
|
| 150 |
206 |
|
|
| 151 |
207 |
|
#[test]
|
| 152 |
|
- |
fn the_tauri_layout_puts_both_where_the_apps_look() {
|
|
208 |
+ |
fn density_is_selected_by_capability_not_by_width_or_agent() {
|
|
209 |
+ |
let dir = scratch("density");
|
|
210 |
+ |
let path = dir.join("geometry.css");
|
|
211 |
+ |
geometry_css(&path, None);
|
|
212 |
+ |
let css = std::fs::read_to_string(&path).unwrap();
|
|
213 |
+ |
assert!(css.contains("@media (hover: none), (pointer: coarse)"));
|
|
214 |
+ |
// The three things density must never be selected by.
|
|
215 |
+ |
assert!(!css.contains("max-width"), "a breakpoint crept in");
|
|
216 |
+ |
assert!(!css.contains("min-width"), "a breakpoint crept in");
|
|
217 |
+ |
assert!(!css.contains("ui-mode"), "a device mode crept in");
|
|
218 |
+ |
}
|
|
219 |
+ |
|
|
220 |
+ |
#[test]
|
|
221 |
+ |
fn an_explicit_choice_is_emitted_after_the_detection() {
|
|
222 |
+ |
let dir = scratch("explicit");
|
|
223 |
+ |
let path = dir.join("geometry.css");
|
|
224 |
+ |
geometry_css(&path, Some(".ui-mode-mobile"));
|
|
225 |
+ |
let css = std::fs::read_to_string(&path).unwrap();
|
|
226 |
+ |
let media = css.find("@media").expect("media query");
|
|
227 |
+ |
let explicit = css.find(".ui-mode-mobile").expect("explicit selector");
|
|
228 |
+ |
// Equal specificity, so order is the whole mechanism: the user's
|
|
229 |
+ |
// choice has to come last or detection quietly overrides it.
|
|
230 |
+ |
assert!(explicit > media, "the explicit selector must come last");
|
|
231 |
+ |
}
|
|
232 |
+ |
|
|
233 |
+ |
#[test]
|
|
234 |
+ |
fn no_explicit_selector_means_no_extra_rule() {
|
|
235 |
+ |
let dir = scratch("noexplicit");
|
|
236 |
+ |
let path = dir.join("geometry.css");
|
|
237 |
+ |
geometry_css(&path, None);
|
|
238 |
+ |
let css = std::fs::read_to_string(&path).unwrap();
|
|
239 |
+ |
assert_eq!(
|
|
240 |
+ |
css.matches("--gap-peer").count(),
|
|
241 |
+ |
2,
|
|
242 |
+ |
"pointer and touch, no more"
|
|
243 |
+ |
);
|
|
244 |
+ |
}
|
|
245 |
+ |
|
|
246 |
+ |
#[test]
|
|
247 |
+ |
fn the_tauri_layout_puts_all_three_where_the_apps_look() {
|
| 153 |
248 |
|
let root = scratch("tauri");
|
| 154 |
249 |
|
std::fs::create_dir_all(root.join("frontend").join("css")).unwrap();
|
| 155 |
|
- |
tauri_frontend(&root, &makeover_webview::Emit::default());
|
|
250 |
+ |
tauri_frontend(&root, &makeover_webview::Emit::default(), None);
|
|
251 |
+ |
assert!(
|
|
252 |
+ |
root.join("frontend")
|
|
253 |
+ |
.join("css")
|
|
254 |
+ |
.join("geometry.css")
|
|
255 |
+ |
.exists()
|
|
256 |
+ |
);
|
| 156 |
257 |
|
assert!(
|
| 157 |
258 |
|
root.join("frontend")
|
| 158 |
259 |
|
.join("css")
|