| ... | @@ -3,7 +3,7 @@ | ... | @@ -3,7 +3,7 @@ |
| 3 | //! chosen. | 3 | //! chosen. |
| 4 | | 4 | |
| 5 | use crate::{ | 5 | use crate::{ |
| 6 | State, UserEvent, platform, | 6 | State, UserEvent, |
| 7 | settings::{Backend, ColorScheme}, | 7 | settings::{Backend, ColorScheme}, |
| 8 | update, | 8 | update, |
| 9 | }; | 9 | }; |
| ... | @@ -12,15 +12,14 @@ use canvas::editor::DefaultFont; | ... | @@ -12,15 +12,14 @@ use canvas::editor::DefaultFont; |
| 12 | use ui::{Anchor, Axis, Extent, Flags, Id, Size, Spec, Theme, Ui, fill, popup::Item, px}; | 12 | use ui::{Anchor, Axis, Extent, Flags, Id, Size, Spec, Theme, Ui, fill, popup::Item, px}; |
| 13 | use winit::keyboard::NamedKey; | 13 | use winit::keyboard::NamedKey; |
| 14 | | 14 | |
| 15 | const WIDTH: f32 = 680.0; | 15 | const WIDTH: f32 = 700.0; |
| 16 | const NAV: f32 = 150.0; | 16 | const NAV: f32 = 150.0; |
| 17 | /// The column the fields' labels share. | | |
| 18 | const LABEL: f32 = 104.0; | | |
| 19 | const SCHEMES: [(ColorScheme, &str); 3] = [ | 17 | const SCHEMES: [(ColorScheme, &str); 3] = [ |
| 20 | (ColorScheme::System, "System"), | 18 | (ColorScheme::System, "System"), |
| 21 | (ColorScheme::Light, "Light"), | 19 | (ColorScheme::Light, "Light"), |
| 22 | (ColorScheme::Dark, "Dark"), | 20 | (ColorScheme::Dark, "Dark"), |
| 23 | ]; | 21 | ]; |
| | 22 | const SIDES: [&str; 2] = ["Left", "Right"]; |
| 24 | /// The sizes Default font offers, OneNote 2010's list in points. | 23 | /// The sizes Default font offers, OneNote 2010's list in points. |
| 25 | const SIZES: [f32; 19] = [ | 24 | const SIZES: [f32; 19] = [ |
| 26 | 8.0, 9.0, 9.5, 10.0, 10.5, 11.0, 11.5, 12.0, 14.0, 16.0, 18.0, 20.0, 22.0, 24.0, 26.0, 28.0, | 25 | 8.0, 9.0, 9.5, 10.0, 10.5, 11.0, 11.5, 12.0, 14.0, 16.0, 18.0, 20.0, 22.0, 24.0, 26.0, 28.0, |
| ... | @@ -42,16 +41,35 @@ struct Group { | ... | @@ -42,16 +41,35 @@ struct Group { |
| 42 | | 41 | |
| 43 | struct Row { | 42 | struct Row { |
| 44 | label: &'static str, | 43 | label: &'static str, |
| | 44 | /// A line under the control for what the label can't say; empty for none. |
| | 45 | hint: &'static str, |
| 45 | /// Other words search finds the row by. | 46 | /// Other words search finds the row by. |
| 46 | keywords: &'static str, | 47 | keywords: &'static str, |
| 47 | control: Control, | 48 | control: Control, |
| 48 | } | 49 | } |
| 49 | | 50 | |
| | 51 | impl Row { |
| | 52 | const fn new(label: &'static str, keywords: &'static str, control: Control) -> Self { |
| | 53 | Self { |
| | 54 | label, |
| | 55 | hint: "", |
| | 56 | keywords, |
| | 57 | control, |
| | 58 | } |
| | 59 | } |
| | 60 | |
| | 61 | const fn hint(self, hint: &'static str) -> Self { |
| | 62 | Self { hint, ..self } |
| | 63 | } |
| | 64 | } |
| | 65 | |
| | 66 | /// Every control stands in one column, after the labels' column. |
| 50 | enum Control { | 67 | enum Control { |
| 51 | /// A check box before the label, bound to a choice. | 68 | /// A check box labelled by the row, bound to a choice. |
| 52 | Check(fn(&mut Options) -> &mut bool), | 69 | Check(fn(&mut Options) -> &mut bool), |
| 53 | /// The label in the labels' column, then what the function builds. | 70 | /// The row's label in the labels' column, then what the function builds, given the |
| 54 | Field(fn(&mut State, &mut Options)), | 71 | /// label to name its control by. |
| | 72 | Field(fn(&mut State, &mut Options, &str)), |
| 55 | /// What `build` makes across the row, unlabelled, from the search's words; `finds` tells | 73 | /// What `build` makes across the row, unlabelled, from the search's words; `finds` tells |
| 56 | /// whether they find anything in it. Words found in the row's own texts give it none. | 74 | /// whether they find anything in it. Words found in the row's own texts give it none. |
| 57 | Block { | 75 | Block { |
| ... | @@ -60,33 +78,33 @@ enum Control { | ... | @@ -60,33 +78,33 @@ enum Control { |
| 60 | }, | 78 | }, |
| 61 | } | 79 | } |
| 62 | | 80 | |
| 63 | /// Every section, in the list's order. | 81 | /// Every section, in the list's order, with only the rows that apply here. |
| 64 | const SECTIONS: &[Section] = &[ | 82 | const SECTIONS: &[Section] = &[ |
| 65 | Section { | 83 | Section { |
| 66 | name: "General", | 84 | name: "General", |
| 67 | groups: &[ | 85 | groups: &[ |
| 68 | Group { | 86 | Group { |
| 69 | heading: "User Interface Options", | 87 | heading: "Interface", |
| 70 | rows: &[ | 88 | rows: &[ |
| 71 | Row { | 89 | Row::new( |
| 72 | label: "Appearance:", | 90 | "Appearance:", |
| 73 | keywords: "color colour scheme dark light mode system theme", | 91 | "color colour scheme dark light mode system theme", |
| 74 | control: Control::Field(appearance), | 92 | Control::Field(appearance), |
| 75 | }, | 93 | ), |
| 76 | Row { | 94 | Row::new( |
| 77 | label: "Pages match UI theme", | 95 | "Pages match the appearance", |
| 78 | keywords: "dark white paper background", | 96 | "dark white paper background ui theme", |
| 79 | control: Control::Check(|options| &mut options.pages_match), | 97 | Control::Check(|options| &mut options.pages_match), |
| 80 | }, | 98 | ) |
| | 99 | .hint("Turned off, pages stay white"), |
| 81 | ], | 100 | ], |
| 82 | }, | 101 | }, |
| 83 | Group { | 102 | Group { |
| 84 | heading: "Personalize", | 103 | heading: "Personalize", |
| 85 | rows: &[Row { | 104 | rows: &[ |
| 86 | label: "User name:", | 105 | Row::new("User name:", "author", Control::Field(user_name_field)) |
| 87 | keywords: "author name", | 106 | .hint("Shown with your edits in shared notebooks"), |
| 88 | control: Control::Field(user_name_field), | 107 | ], |
| 89 | }], | | |
| 90 | }, | 108 | }, |
| 91 | ], | 109 | ], |
| 92 | }, | 110 | }, |
| ... | @@ -96,76 +114,68 @@ const SECTIONS: &[Section] = &[ | ... | @@ -96,76 +114,68 @@ const SECTIONS: &[Section] = &[ |
| 96 | Group { | 114 | Group { |
| 97 | heading: "Default font", | 115 | heading: "Default font", |
| 98 | rows: &[ | 116 | rows: &[ |
| 99 | Row { | 117 | Row::new( |
| 100 | label: "Font:", | 118 | "Font:", |
| 101 | keywords: "typeface text new", | 119 | "typeface size points text new", |
| 102 | control: Control::Field(font_face), | 120 | Control::Field(font_face), |
| 103 | }, | 121 | ), |
| 104 | Row { | 122 | Row::new( |
| 105 | label: "Size:", | 123 | "Color:", |
| 106 | keywords: "font points text new", | 124 | "font colour text new automatic", |
| 107 | control: Control::Field(font_size), | 125 | Control::Field(font_color), |
| 108 | }, | 126 | ), |
| 109 | Row { | | |
| 110 | label: "Font color:", | | |
| 111 | keywords: "colour text new automatic", | | |
| 112 | control: Control::Field(font_color), | | |
| 113 | }, | | |
| 114 | ], | 127 | ], |
| 115 | }, | 128 | }, |
| 116 | Group { | 129 | Group { |
| 117 | heading: "Default style theme", | 130 | heading: "Font styles", |
| 118 | rows: &[Row { | 131 | rows: &[Row::new( |
| 119 | label: "New notebooks:", | 132 | "New notebooks:", |
| 120 | keywords: "default for new notebooks styles headings modern", | 133 | "default style theme headings modern", |
| 121 | control: Control::Field(notebook_theme), | 134 | Control::Field(notebook_theme), |
| 122 | }], | 135 | ) |
| | 136 | .hint("The styles a new notebook starts with")], |
| 123 | }, | 137 | }, |
| 124 | Group { | 138 | Group { |
| 125 | heading: "Proofing", | 139 | heading: "Proofing", |
| 126 | rows: &[Row { | 140 | rows: &[Row::new( |
| 127 | label: "Hide spelling errors", | 141 | "Hide spelling errors", |
| 128 | keywords: "spell check misspelled words", | 142 | "spell check misspelled words", |
| 129 | control: Control::Check(|options| &mut options.hide_spelling), | 143 | Control::Check(|options| &mut options.hide_spelling), |
| 130 | }], | 144 | )], |
| 131 | }, | 145 | }, |
| 132 | Group { | 146 | Group { |
| 133 | heading: "AutoFormat", | 147 | heading: "AutoFormat", |
| 134 | rows: &[Row { | 148 | rows: &[Row::new( |
| 135 | label: "Markdown shortcuts", | 149 | "Markdown shortcuts", |
| 136 | keywords: "markdown autoformat heading bullet numbering list to do quote \ | 150 | "markdown autoformat heading bullet numbering list to do quote bold \ |
| 137 | bold italic strikethrough code typing", | 151 | italic strikethrough code typing", |
| 138 | control: Control::Check(|options| &mut options.markdown), | 152 | Control::Check(|options| &mut options.markdown), |
| 139 | }], | 153 | ) |
| | 154 | .hint("Type # for a heading, - for a bullet, **bold** and more")], |
| 140 | }, | 155 | }, |
| 141 | Group { | 156 | Group { |
| 142 | heading: "Pen", | 157 | heading: "Pen", |
| 143 | rows: &[Row { | 158 | rows: &[Row::new( |
| 144 | label: "Use pen pressure sensitivity", | 159 | "Use pen pressure sensitivity", |
| 145 | keywords: "tablet stylus ink stroke width drawing", | 160 | "tablet stylus ink stroke width drawing", |
| 146 | control: Control::Check(|options| &mut options.pen_pressure), | 161 | Control::Check(|options| &mut options.pen_pressure), |
| 147 | }], | 162 | )], |
| 148 | }, | 163 | }, |
| 149 | Group { | 164 | Group { |
| 150 | heading: "Passwords", | 165 | heading: "Passwords", |
| 151 | rows: &[ | 166 | rows: &[ |
| 152 | Row { | 167 | Row::new( |
| 153 | label: "Lock password protected sections after I have not worked in \ | 168 | "Lock when idle for:", |
| 154 | them for:", | 169 | "password protect protected lock unlock idle minutes hours timeout \ |
| 155 | keywords: "protect protected lock unlock idle minutes timeout security", | 170 | security", |
| 156 | control: Control::Check(|options| &mut options.lock_idle), | 171 | Control::Field(lock_after), |
| 157 | }, | 172 | ) |
| 158 | Row { | 173 | .hint("Password-protected sections you haven't worked in"), |
| 159 | label: "Amount of time:", | 174 | Row::new( |
| 160 | keywords: "password protect protected lock idle minutes hours timeout", | 175 | "Lock when I leave a section", |
| 161 | control: Control::Field(lock_after), | 176 | "password protect protected lock navigate away switch security", |
| 162 | }, | 177 | Control::Check(|options| &mut options.lock_on_leave), |
| 163 | Row { | 178 | ), |
| 164 | label: "Lock password protected sections as soon as I navigate away \ | | |
| 165 | from them", | | |
| 166 | keywords: "password protect protected lock leave switch security", | | |
| 167 | control: Control::Check(|options| &mut options.lock_on_leave), | | |
| 168 | }, | | |
| 169 | ], | 179 | ], |
| 170 | }, | 180 | }, |
| 171 | ], | 181 | ], |
| ... | @@ -175,55 +185,61 @@ const SECTIONS: &[Section] = &[ | ... | @@ -175,55 +185,61 @@ const SECTIONS: &[Section] = &[ |
| 175 | groups: &[Group { | 185 | groups: &[Group { |
| 176 | heading: "", | 186 | heading: "", |
| 177 | rows: &[ | 187 | rows: &[ |
| 178 | Row { | 188 | Row::new( |
| 179 | label: "Page tabs appear on the left", | 189 | "Page tabs:", |
| 180 | keywords: "pages list side layout", | 190 | "pages list side left right layout", |
| 181 | control: Control::Check(|options| &mut options.page_tabs_left), | 191 | Control::Field(|state, options, name| { |
| 182 | }, | 192 | side(state, name, &mut options.page_tabs_left) |
| 183 | Row { | 193 | }), |
| 184 | label: "Navigation bar appears on the left", | 194 | ), |
| 185 | keywords: "notebooks sidebar side right layout", | 195 | Row::new( |
| 186 | control: Control::Check(|options| &mut options.navigation_bar_left), | 196 | "Notebooks:", |
| 187 | }, | 197 | "navigation bar sidebar side left right layout", |
| 188 | Row { | 198 | Control::Field(|state, options, name| { |
| 189 | label: "Renderer:", | 199 | side(state, name, &mut options.navigation_bar_left) |
| 190 | keywords: "graphics gpu backend drawing metal opengl vulkan direct3d \ | 200 | }), |
| 191 | webgpu webgl canvas", | 201 | ), |
| 192 | control: Control::Field(renderer), | 202 | Row::new( |
| 193 | }, | 203 | "Renderer:", |
| | 204 | "graphics gpu backend drawing metal opengl vulkan direct3d webgpu webgl \ |
| | 205 | canvas", |
| | 206 | Control::Field(renderer), |
| | 207 | ), |
| 194 | ], | 208 | ], |
| 195 | }], | 209 | }], |
| 196 | }, | 210 | }, |
| 197 | Section { | 211 | Section { |
| 198 | name: "Sync & Storage", | 212 | name: "Sync & Storage", |
| 199 | groups: &[ | 213 | groups: &[ |
| | 214 | #[cfg(not(target_arch = "wasm32"))] |
| 200 | Group { | 215 | Group { |
| 201 | heading: "Cache file location", | 216 | heading: "Cache", |
| 202 | rows: &[Row { | 217 | rows: &[Row::new( |
| 203 | label: "Path:", | 218 | "Location:", |
| 204 | keywords: "folder replica data disk", | 219 | "cache file folder path replica data disk", |
| 205 | control: Control::Field(cache), | 220 | Control::Field(cache), |
| 206 | }], | 221 | ) |
| | 222 | .hint("Where Snowbound keeps a copy of each section it syncs")], |
| 207 | }, | 223 | }, |
| 208 | #[cfg(feature = "live")] | 224 | #[cfg(feature = "live")] |
| 209 | Group { | 225 | Group { |
| 210 | heading: "Live Share", | 226 | heading: "Live Share", |
| 211 | rows: &[ | 227 | rows: &[ |
| 212 | Row { | 228 | Row::new( |
| 213 | label: "Show others where I am in shared notebooks", | 229 | "Show others where I am in shared notebooks", |
| 214 | keywords: "presence privacy cursor caret avatar collaborate people", | 230 | "presence privacy cursor caret avatar collaborate people", |
| 215 | control: Control::Check(|options| &mut options.presence), | 231 | Control::Check(|options| &mut options.presence), |
| 216 | }, | 232 | ), |
| 217 | Row { | 233 | Row::new( |
| 218 | label: "Show my account picture with my name", | 234 | "Show my account picture with my name", |
| 219 | keywords: "presence privacy photo avatar", | 235 | "presence privacy photo avatar", |
| 220 | control: Control::Check(|options| &mut options.picture), | 236 | Control::Check(|options| &mut options.picture), |
| 221 | }, | 237 | ), |
| 222 | Row { | 238 | Row::new( |
| 223 | label: "Relay:", | 239 | "Relay:", |
| 224 | keywords: "live share server internet network presence", | 240 | "live share server internet network presence", |
| 225 | control: Control::Field(relay), | 241 | Control::Field(relay), |
| 226 | }, | 242 | ), |
| 227 | ], | 243 | ], |
| 228 | }, | 244 | }, |
| 229 | ], | 245 | ], |
| ... | @@ -233,22 +249,19 @@ const SECTIONS: &[Section] = &[ | ... | @@ -233,22 +249,19 @@ const SECTIONS: &[Section] = &[ |
| 233 | groups: &[Group { | 249 | groups: &[Group { |
| 234 | heading: "", | 250 | heading: "", |
| 235 | rows: &[ | 251 | rows: &[ |
| 236 | Row { | 252 | Row::new("Version:", "about build release", Control::Field(version)), |
| 237 | label: "Version:", | 253 | #[cfg(not(target_arch = "wasm32"))] |
| 238 | keywords: "about build release", | 254 | Row::new( |
| 239 | control: Control::Field(version), | 255 | "Check for updates automatically", |
| 240 | }, | 256 | "update download", |
| 241 | Row { | 257 | Control::Check(|options| &mut options.automatic_updates), |
| 242 | label: "Check for updates automatically", | 258 | ), |
| 243 | keywords: "update download", | | |
| 244 | control: Control::Check(|options| &mut options.automatic_updates), | | |
| 245 | }, | | |
| 246 | #[cfg(target_os = "linux")] | 259 | #[cfg(target_os = "linux")] |
| 247 | Row { | 260 | Row::new( |
| 248 | label: "Installed:", | 261 | "Installed:", |
| 249 | keywords: "install uninstall app menu remove", | 262 | "install uninstall app menu remove", |
| 250 | control: Control::Field(installed), | 263 | Control::Field(installed), |
| 251 | }, | 264 | ), |
| 252 | ], | 265 | ], |
| 253 | }], | 266 | }], |
| 254 | }, | 267 | }, |
| ... | @@ -256,14 +269,14 @@ const SECTIONS: &[Section] = &[ | ... | @@ -256,14 +269,14 @@ const SECTIONS: &[Section] = &[ |
| 256 | name: "Keyboard", | 269 | name: "Keyboard", |
| 257 | groups: &[Group { | 270 | groups: &[Group { |
| 258 | heading: "", | 271 | heading: "", |
| 259 | rows: &[Row { | 272 | rows: &[Row::new( |
| 260 | label: "Shortcuts", | 273 | "Shortcuts", |
| 261 | keywords: "keys chords hotkeys bindings commands menus", | 274 | "keys chords hotkeys bindings commands menus", |
| 262 | control: Control::Block { | 275 | Control::Block { |
| 263 | build: |state, options, query| options.keyboard.build(&mut state.ui, query), | 276 | build: |state, options, query| options.keyboard.build(&mut state.ui, query), |
| 264 | finds: crate::keys::searched, | 277 | finds: crate::keys::searched, |
| 265 | }, | 278 | }, |
| 266 | }], | 279 | )], |
| 267 | }], | 280 | }], |
| 268 | }, | 281 | }, |
| 269 | ]; | 282 | ]; |
| ... | @@ -283,9 +296,8 @@ pub struct Options { | ... | @@ -283,9 +296,8 @@ pub struct Options { |
| 283 | default_font: DefaultFont, | 296 | default_font: DefaultFont, |
| 284 | page_tabs_left: bool, | 297 | page_tabs_left: bool, |
| 285 | navigation_bar_left: bool, | 298 | navigation_bar_left: bool, |
| 286 | lock_idle: bool, | 299 | /// Minutes, as `protection::AFTER` lists them; none never locks. |
| 287 | /// Minutes, as `protection::AFTER` lists them. | 300 | lock_after: Option<u32>, |
| 288 | lock_minutes: u32, | | |
| 289 | lock_on_leave: bool, | 301 | lock_on_leave: bool, |
| 290 | presence: bool, | 302 | presence: bool, |
| 291 | picture: bool, | 303 | picture: bool, |
| ... | @@ -312,34 +324,14 @@ fn user_name() -> Id { | ... | @@ -312,34 +324,14 @@ fn user_name() -> Id { |
| 312 | id().child("user-name") | 324 | id().child("user-name") |
| 313 | } | 325 | } |
| 314 | | 326 | |
| 315 | fn schemes() -> Id { | | |
| 316 | id().child("color-schemes") | | |
| 317 | } | | |
| 318 | | | |
| 319 | fn fonts() -> Id { | 327 | fn fonts() -> Id { |
| 320 | id().child("fonts") | 328 | id().child("fonts") |
| 321 | } | 329 | } |
| 322 | | 330 | |
| 323 | fn sizes() -> Id { | | |
| 324 | id().child("sizes") | | |
| 325 | } | | |
| 326 | | | |
| 327 | fn lock_times() -> Id { | | |
| 328 | id().child("lock-times") | | |
| 329 | } | | |
| 330 | | | |
| 331 | fn font_colors() -> Id { | 331 | fn font_colors() -> Id { |
| 332 | id().child("font-colors") | 332 | id().child("font-colors") |
| 333 | } | 333 | } |
| 334 | | 334 | |
| 335 | fn notebook_themes() -> Id { | | |
| 336 | id().child("notebook-themes") | | |
| 337 | } | | |
| 338 | | | |
| 339 | fn renderers() -> Id { | | |
| 340 | id().child("renderers") | | |
| 341 | } | | |
| 342 | | | |
| 343 | /// Whether `word`, lowercase, starts a word of `text`: "pen" finds "Pen" and "pen-like", | 335 | /// Whether `word`, lowercase, starts a word of `text`: "pen" finds "Pen" and "pen-like", |
| 344 | /// not "Open". | 336 | /// not "Open". |
| 345 | fn starts_word(text: &str, word: &str) -> bool { | 337 | fn starts_word(text: &str, word: &str) -> bool { |
| ... | @@ -394,8 +386,7 @@ impl State { | ... | @@ -394,8 +386,7 @@ impl State { |
| 394 | default_font: self.view.editor.default_font.clone(), | 386 | default_font: self.view.editor.default_font.clone(), |
| 395 | page_tabs_left: self.page_tabs_left, | 387 | page_tabs_left: self.page_tabs_left, |
| 396 | navigation_bar_left: !self.navigation_bar_right, | 388 | navigation_bar_left: !self.navigation_bar_right, |
| 397 | lock_idle: self.passwords.lock_after.is_some(), | 389 | lock_after: self.passwords.lock_after, |
| 398 | lock_minutes: self.passwords.lock_after.unwrap_or(10), | | |
| 399 | lock_on_leave: self.passwords.lock_on_leave, | 390 | lock_on_leave: self.passwords.lock_on_leave, |
| 400 | presence: self.live_options.presence, | 391 | presence: self.live_options.presence, |
| 401 | picture: self.live_options.picture, | 392 | picture: self.live_options.picture, |
| ... | @@ -409,7 +400,8 @@ impl State { | ... | @@ -409,7 +400,8 @@ impl State { |
| 409 | } | 400 | } |
| 410 | | 401 | |
| 411 | /// Builds the Options dialog while it is open. OK, or Enter in the user name, keeps its | 402 | /// Builds the Options dialog while it is open. OK, or Enter in the user name, keeps its |
| 412 | /// choices with a non-empty user name; Cancel, Escape or a press outside leave them. | 403 | /// choices, and a user name that isn't blank; Cancel, Escape or a press outside leave |
| | 404 | /// them. |
| 413 | pub(crate) fn options_dialog(&mut self) { | 405 | pub(crate) fn options_dialog(&mut self) { |
| 414 | let Some(mut options) = self.options.take() else { | 406 | let Some(mut options) = self.options.take() else { |
| 415 | return; | 407 | return; |
| ... | @@ -420,6 +412,7 @@ impl State { | ... | @@ -420,6 +412,7 @@ impl State { |
| 420 | let theme = self.ui.theme.clone(); | 412 | let theme = self.ui.theme.clone(); |
| 421 | let row = row_height(&theme); | 413 | let row = row_height(&theme); |
| 422 | let ui = &mut self.ui; | 414 | let ui = &mut self.ui; |
| | 415 | let column = labels_column(ui); |
| 423 | let entered = ui::popup::navigation(ui, &[user_name()], &[NamedKey::Enter]) | 416 | let entered = ui::popup::navigation(ui, &[user_name()], &[NamedKey::Enter]) |
| 424 | .contains(&NamedKey::Enter); | 417 | .contains(&NamedKey::Enter); |
| 425 | // As tall as its contents up to the window, which the list then scrolls within. | 418 | // As tall as its contents up to the window, which the list then scrolls within. |
| ... | @@ -636,28 +629,11 @@ impl State { | ... | @@ -636,28 +629,11 @@ impl State { |
| 636 | ..Spec::default() | 629 | ..Spec::default() |
| 637 | }, | 630 | }, |
| 638 | ); | 631 | ); |
| 639 | match row.control { | 632 | if let Control::Block { build, .. } = row.control { |
| 640 | Control::Check(choice) => { | 633 | let titled = titled(&query, section, group, row); |
| 641 | let checked = choice(&mut options); | 634 | build(self, &mut options, if titled { &[] } else { &query }); |
| 642 | if ui::check_box(&mut self.ui, "check", row.label, *checked).clicked { | 635 | } else { |
| 643 | *checked = !*checked; | 636 | self.row(&mut options, row, column); |
| 644 | } | | |
| 645 | } | | |
| 646 | Control::Field(build) => { | | |
| 647 | self.ui.leaf( | | |
| 648 | "label", | | |
| 649 | Spec { | | |
| 650 | size: [px(LABEL), px(row_height(&theme))], | | |
| 651 | text: Some(row.label), | | |
| 652 | ..Spec::default() | | |
| 653 | }, | | |
| 654 | ); | | |
| 655 | build(self, &mut options); | | |
| 656 | } | | |
| 657 | Control::Block { build, .. } => { | | |
| 658 | let titled = titled(&query, section, group, row); | | |
| 659 | build(self, &mut options, if titled { &[] } else { &query }); | | |
| 660 | } | | |
| 661 | } | 637 | } |
| 662 | self.ui.close(); | 638 | self.ui.close(); |
| 663 | } | 639 | } |
| ... | @@ -694,9 +670,11 @@ impl State { | ... | @@ -694,9 +670,11 @@ impl State { |
| 694 | let ok = ok || entered; | 670 | let ok = ok || entered; |
| 695 | ui.close(); | 671 | ui.close(); |
| 696 | ui.close(); | 672 | ui.close(); |
| 697 | let name = options.user_name.trim(); | 673 | if ok { |
| 698 | if ok && !name.is_empty() { | 674 | let name = options.user_name.trim(); |
| 699 | self.author = name.to_owned(); | 675 | if !name.is_empty() { |
| | 676 | self.author = name.to_owned(); |
| | 677 | } |
| 700 | self.color_scheme = options.color_scheme; | 678 | self.color_scheme = options.color_scheme; |
| 701 | self.light_pages = !options.pages_match; | 679 | self.light_pages = !options.pages_match; |
| 702 | self.hide_spelling = options.hide_spelling; | 680 | self.hide_spelling = options.hide_spelling; |
| ... | @@ -706,7 +684,7 @@ impl State { | ... | @@ -706,7 +684,7 @@ impl State { |
| 706 | self.page_tabs_left = options.page_tabs_left; | 684 | self.page_tabs_left = options.page_tabs_left; |
| 707 | self.navigation_bar_right = !options.navigation_bar_left; | 685 | self.navigation_bar_right = !options.navigation_bar_left; |
| 708 | self.passwords = crate::settings::Passwords { | 686 | self.passwords = crate::settings::Passwords { |
| 709 | lock_after: options.lock_idle.then_some(options.lock_minutes), | 687 | lock_after: options.lock_after, |
| 710 | lock_on_leave: options.lock_on_leave, | 688 | lock_on_leave: options.lock_on_leave, |
| 711 | }; | 689 | }; |
| 712 | self.notebook_theme = options.notebook_theme; | 690 | self.notebook_theme = options.notebook_theme; |
| ... | @@ -734,10 +712,79 @@ impl State { | ... | @@ -734,10 +712,79 @@ impl State { |
| 734 | } | 712 | } |
| 735 | self.ui.close_popup(id()); | 713 | self.ui.close_popup(id()); |
| 736 | } | 714 | } |
| | 715 | |
| | 716 | /// A row's label in the labels' column `column` wide, then its control over its hint. |
| | 717 | fn row(&mut self, options: &mut Options, row: &Row, column: f32) { |
| | 718 | let theme = self.ui.theme.clone(); |
| | 719 | let height = row_height(&theme); |
| | 720 | let field = matches!(row.control, Control::Field(_)); |
| | 721 | self.ui.leaf( |
| | 722 | "label", |
| | 723 | Spec { |
| | 724 | size: [px(column), px(height)], |
| | 725 | text: field.then_some(row.label), |
| | 726 | ..Spec::default() |
| | 727 | }, |
| | 728 | ); |
| | 729 | self.ui.open( |
| | 730 | "control", |
| | 731 | Spec { |
| | 732 | axis: Axis::Y, |
| | 733 | size: [fill(), ui::children()], |
| | 734 | ..Spec::default() |
| | 735 | }, |
| | 736 | ); |
| | 737 | match row.control { |
| | 738 | Control::Check(choice) => { |
| | 739 | let checked = choice(options); |
| | 740 | if ui::check_box(&mut self.ui, "check", row.label, *checked).clicked { |
| | 741 | *checked = !*checked; |
| | 742 | } |
| | 743 | } |
| | 744 | Control::Field(build) => { |
| | 745 | self.ui.open( |
| | 746 | "field", |
| | 747 | Spec { |
| | 748 | size: [fill(), ui::children()], |
| | 749 | gap: 8.0, |
| | 750 | ..Spec::default() |
| | 751 | }, |
| | 752 | ); |
| | 753 | build(self, options, row.label.trim_end_matches(':')); |
| | 754 | self.ui.close(); |
| | 755 | } |
| | 756 | Control::Block { .. } => {} |
| | 757 | } |
| | 758 | if !row.hint.is_empty() { |
| | 759 | self.ui.leaf( |
| | 760 | "hint", |
| | 761 | Spec { |
| | 762 | size: [fill(), ui::fit()], |
| | 763 | text: Some(row.hint), |
| | 764 | font_size: Some((theme.font_size * 0.92).round()), |
| | 765 | color: Some(theme.text_dim), |
| | 766 | overflow: ui::Overflow::Wrap, |
| | 767 | pad: [0.0, 3.0], |
| | 768 | ..Spec::default() |
| | 769 | }, |
| | 770 | ); |
| | 771 | } |
| | 772 | self.ui.close(); |
| | 773 | } |
| | 774 | } |
| | 775 | |
| | 776 | /// How wide the labels' column stands: as the widest field label, so every control lines up. |
| | 777 | fn labels_column(ui: &mut Ui) -> f32 { |
| | 778 | let labels = (SECTIONS.iter()) |
| | 779 | .flat_map(|section| section.groups) |
| | 780 | .flat_map(|group| group.rows) |
| | 781 | .filter(|row| matches!(row.control, Control::Field(_))); |
| | 782 | let widest = labels.fold(0.0_f32, |widest, row| widest.max(ui.measure(row.label)[0])); |
| | 783 | (widest + 12.0).ceil() |
| 737 | } | 784 | } |
| 738 | | 785 | |
| 739 | #[cfg(feature = "live")] | 786 | #[cfg(feature = "live")] |
| 740 | fn relay(state: &mut State, options: &mut Options) { | 787 | fn relay(state: &mut State, options: &mut Options, name: &str) { |
| 741 | let ui = &mut state.ui; | 788 | let ui = &mut state.ui; |
| 742 | let theme = ui.theme.clone(); | 789 | let theme = ui.theme.clone(); |
| 743 | let field = id().child("relay"); | 790 | let field = id().child("relay"); |
| ... | @@ -756,7 +803,7 @@ fn relay(state: &mut State, options: &mut Options) { | ... | @@ -756,7 +803,7 @@ fn relay(state: &mut State, options: &mut Options) { |
| 756 | }, | 803 | }, |
| 757 | ); | 804 | ); |
| 758 | if let Some(node) = ui.access(field) { | 805 | if let Some(node) = ui.access(field) { |
| 759 | node.set_label("Live Share relay"); | 806 | node.set_label(name); |
| 760 | } | 807 | } |
| 761 | } | 808 | } |
| 762 | | 809 | |
| ... | @@ -806,62 +853,60 @@ fn heading(ui: &mut Ui, theme: &Theme, text: &str, lit: bool) { | ... | @@ -806,62 +853,60 @@ fn heading(ui: &mut Ui, theme: &Theme, text: &str, lit: bool) { |
| 806 | ); | 853 | ); |
| 807 | } | 854 | } |
| 808 | | 855 | |
| 809 | fn appearance(state: &mut State, options: &mut Options) { | 856 | /// A combo named `name` showing `choices[chosen]`, `width` wide, whose menu picks another. |
| 810 | let ui = &mut state.ui; | 857 | fn dropdown(ui: &mut Ui, name: &str, choices: &[&str], chosen: usize, width: f32) -> Option<usize> { |
| | 858 | let menu = id().child(name); |
| 811 | let combo = ui.id("combo"); | 859 | let combo = ui.id("combo"); |
| 812 | let current = SCHEMES | 860 | let current = choices.get(chosen).copied().unwrap_or_default(); |
| 813 | .iter() | 861 | ui::shell::combo(ui, "combo", name, current, width, menu, true); |
| 814 | .find(|(scheme, _)| *scheme == options.color_scheme) | 862 | let items: Vec<Item> = (choices.iter().enumerate()) |
| 815 | .map_or("", |(_, name)| name); | 863 | .map(|(index, text)| Item { |
| 816 | ui::shell::combo(ui, "combo", "Appearance", current, 140.0, schemes(), true); | 864 | text, |
| 817 | let items = SCHEMES.map(|(scheme, name)| Item { | 865 | checked: Some(index == chosen), |
| 818 | text: name, | 866 | current: index == chosen, |
| 819 | checked: Some(scheme == options.color_scheme), | 867 | ..Item::default() |
| 820 | current: scheme == options.color_scheme, | 868 | }) |
| 821 | ..Item::default() | 869 | .collect(); |
| 822 | }); | | |
| 823 | let anchor = Anchor::Below(ui.rect(combo).unwrap_or_default()); | 870 | let anchor = Anchor::Below(ui.rect(combo).unwrap_or_default()); |
| 824 | if let Some(index) = ui::popup::menu(ui, schemes(), anchor, &items, None) { | 871 | ui::popup::menu(ui, menu, anchor, &items, None) |
| | 872 | } |
| | 873 | |
| | 874 | fn appearance(state: &mut State, options: &mut Options, name: &str) { |
| | 875 | let chosen = (SCHEMES.iter()) |
| | 876 | .position(|(scheme, _)| *scheme == options.color_scheme) |
| | 877 | .unwrap_or_default(); |
| | 878 | let choices = SCHEMES.map(|(_, label)| label); |
| | 879 | if let Some(index) = ui::segmented(&mut state.ui, "schemes", name, &choices, chosen) { |
| 825 | options.color_scheme = SCHEMES[index].0; | 880 | options.color_scheme = SCHEMES[index].0; |
| 826 | } | 881 | } |
| 827 | } | 882 | } |
| 828 | | 883 | |
| 829 | fn notebook_theme(state: &mut State, options: &mut Options) { | 884 | /// Left or Right, for a pane on the left where `left`. |
| 830 | let ui = &mut state.ui; | 885 | fn side(state: &mut State, name: &str, left: &mut bool) { |
| 831 | let combo = ui.id("combo"); | 886 | if let Some(index) = ui::segmented(&mut state.ui, "side", name, &SIDES, usize::from(!*left)) { |
| | 887 | *left = index == 0; |
| | 888 | } |
| | 889 | } |
| | 890 | |
| | 891 | fn notebook_theme(state: &mut State, options: &mut Options, name: &str) { |
| 832 | let themes = notebook::sidecar::themes::built_in(); | 892 | let themes = notebook::sidecar::themes::built_in(); |
| 833 | let choices: Vec<(Option<&str>, &str)> = std::iter::once((None, "No Theme")) | 893 | let ids: Vec<Option<&str>> = std::iter::once(None) |
| 834 | .chain( | 894 | .chain(themes.iter().map(|theme| Some(theme.id.as_str()))) |
| 835 | themes | | |
| 836 | .iter() | | |
| 837 | .map(|theme| (Some(theme.id.as_str()), theme.name.as_str())), | | |
| 838 | ) | | |
| 839 | .collect(); | 895 | .collect(); |
| 840 | let chosen = options.notebook_theme.as_deref(); | 896 | let labels: Vec<&str> = std::iter::once("No Theme") |
| 841 | let current = choices | 897 | .chain(themes.iter().map(|theme| theme.name.as_str())) |
| 842 | .iter() | | |
| 843 | .find(|(id, _)| *id == chosen) | | |
| 844 | .map_or("", |(_, name)| name); | | |
| 845 | let title = "Default for new notebooks"; | | |
| 846 | ui::shell::combo(ui, "combo", title, current, 140.0, notebook_themes(), true); | | |
| 847 | let items: Vec<Item> = choices | | |
| 848 | .iter() | | |
| 849 | .map(|(id, name)| Item { | | |
| 850 | text: name, | | |
| 851 | checked: Some(*id == chosen), | | |
| 852 | current: *id == chosen, | | |
| 853 | ..Item::default() | | |
| 854 | }) | | |
| 855 | .collect(); | 898 | .collect(); |
| 856 | let anchor = Anchor::Below(ui.rect(combo).unwrap_or_default()); | 899 | let chosen = (ids.iter()) |
| 857 | if let Some(index) = ui::popup::menu(ui, notebook_themes(), anchor, &items, None) { | 900 | .position(|id| *id == options.notebook_theme.as_deref()) |
| 858 | options.notebook_theme = choices[index].0.map(Into::into); | 901 | .unwrap_or_default(); |
| | 902 | if let Some(index) = dropdown(&mut state.ui, name, &labels, chosen, 160.0) { |
| | 903 | options.notebook_theme = ids[index].map(Into::into); |
| 859 | } | 904 | } |
| 860 | } | 905 | } |
| 861 | | 906 | |
| 862 | /// The platform's backends, the one the default starts with marked, those that didn't start | 907 | /// The platform's backends, the one the default starts with marked, those that didn't start |
| 863 | /// this run disabled; then the backend and adapter drawing now. | 908 | /// this run disabled; then the backend and adapter drawing now. |
| 864 | fn renderer(state: &mut State, options: &mut Options) { | 909 | fn renderer(state: &mut State, options: &mut Options, name: &str) { |
| 865 | let State { | 910 | let State { |
| 866 | ui, | 911 | ui, |
| 867 | unavailable, | 912 | unavailable, |
| ... | @@ -892,7 +937,8 @@ fn renderer(state: &mut State, options: &mut Options) { | ... | @@ -892,7 +937,8 @@ fn renderer(state: &mut State, options: &mut Options) { |
| 892 | let labels: Vec<String> = Backend::PLATFORM.iter().copied().map(label).collect(); | 937 | let labels: Vec<String> = Backend::PLATFORM.iter().copied().map(label).collect(); |
| 893 | let current = shown.map(label).unwrap_or_default(); | 938 | let current = shown.map(label).unwrap_or_default(); |
| 894 | let combo = ui.id("combo"); | 939 | let combo = ui.id("combo"); |
| 895 | ui::shell::combo(ui, "combo", "Renderer", &current, 160.0, renderers(), true); | 940 | let menu = id().child(name); |
| | 941 | ui::shell::combo(ui, "combo", name, &current, 160.0, menu, true); |
| 896 | let items: Vec<Item> = (Backend::PLATFORM.iter().zip(&labels)) | 942 | let items: Vec<Item> = (Backend::PLATFORM.iter().zip(&labels)) |
| 897 | .map(|(backend, text)| Item { | 943 | .map(|(backend, text)| Item { |
| 898 | text, | 944 | text, |
| ... | @@ -904,7 +950,7 @@ fn renderer(state: &mut State, options: &mut Options) { | ... | @@ -904,7 +950,7 @@ fn renderer(state: &mut State, options: &mut Options) { |
| 904 | }) | 950 | }) |
| 905 | .collect(); | 951 | .collect(); |
| 906 | let anchor = Anchor::Below(ui.rect(combo).unwrap_or_default()); | 952 | let anchor = Anchor::Below(ui.rect(combo).unwrap_or_default()); |
| 907 | if let Some(index) = ui::popup::menu(ui, renderers(), anchor, &items, None) { | 953 | if let Some(index) = ui::popup::menu(ui, menu, anchor, &items, None) { |
| 908 | let picked = Backend::PLATFORM[index]; | 954 | let picked = Backend::PLATFORM[index]; |
| 909 | options.renderer = if Some(picked) == automatic { | 955 | options.renderer = if Some(picked) == automatic { |
| 910 | Backend::Default | 956 | Backend::Default |
| ... | @@ -928,36 +974,22 @@ fn renderer(state: &mut State, options: &mut Options) { | ... | @@ -928,36 +974,22 @@ fn renderer(state: &mut State, options: &mut Options) { |
| 928 | ui.leaf("adapter", spec); | 974 | ui.leaf("adapter", spec); |
| 929 | } | 975 | } |
| 930 | | 976 | |
| 931 | fn lock_after(state: &mut State, options: &mut Options) { | 977 | /// Never, or one of OneNote's times, `protection::AFTER`. |
| 932 | let ui = &mut state.ui; | 978 | fn lock_after(state: &mut State, options: &mut Options, name: &str) { |
| 933 | let combo = ui.id("combo"); | 979 | let after = crate::protection::AFTER; |
| 934 | let current = crate::protection::AFTER | 980 | let labels: Vec<&str> = std::iter::once("Never") |
| 935 | .iter() | 981 | .chain(after.iter().map(|(_, label)| *label)) |
| 936 | .find(|(minutes, _)| *minutes == options.lock_minutes) | 982 | .collect(); |
| 937 | .map_or("", |(_, name)| name); | 983 | let chosen = options |
| 938 | ui::shell::combo( | 984 | .lock_after |
| 939 | ui, | 985 | .and_then(|minutes| after.iter().position(|(each, _)| *each == minutes)) |
| 940 | "combo", | 986 | .map_or(0, |index| index + 1); |
| 941 | "Amount of time", | 987 | if let Some(index) = dropdown(&mut state.ui, name, &labels, chosen, 160.0) { |
| 942 | current, | 988 | options.lock_after = index.checked_sub(1).map(|index| after[index].0); |
| 943 | 140.0, | | |
| 944 | lock_times(), | | |
| 945 | true, | | |
| 946 | ); | | |
| 947 | let items = crate::protection::AFTER.map(|(minutes, name)| Item { | | |
| 948 | text: name, | | |
| 949 | checked: Some(minutes == options.lock_minutes), | | |
| 950 | current: minutes == options.lock_minutes, | | |
| 951 | ..Item::default() | | |
| 952 | }); | | |
| 953 | let anchor = Anchor::Below(ui.rect(combo).unwrap_or_default()); | | |
| 954 | if let Some(index) = ui::popup::menu(ui, lock_times(), anchor, &items, None) { | | |
| 955 | options.lock_minutes = crate::protection::AFTER[index].0; | | |
| 956 | options.lock_idle = true; | | |
| 957 | } | 989 | } |
| 958 | } | 990 | } |
| 959 | | 991 | |
| 960 | fn user_name_field(state: &mut State, options: &mut Options) { | 992 | fn user_name_field(state: &mut State, options: &mut Options, name: &str) { |
| 961 | let ui = &mut state.ui; | 993 | let ui = &mut state.ui; |
| 962 | let theme = &ui.theme; | 994 | let theme = &ui.theme; |
| 963 | let width = Extent { | 995 | let width = Extent { |
| ... | @@ -974,53 +1006,52 @@ fn user_name_field(state: &mut State, options: &mut Options) { | ... | @@ -974,53 +1006,52 @@ fn user_name_field(state: &mut State, options: &mut Options) { |
| 974 | }; | 1006 | }; |
| 975 | ui::text_field(ui, user_name(), &mut options.user_name, "", spec); | 1007 | ui::text_field(ui, user_name(), &mut options.user_name, "", spec); |
| 976 | if let Some(node) = ui.access(user_name()) { | 1008 | if let Some(node) = ui.access(user_name()) { |
| 977 | node.set_label("User name"); | 1009 | node.set_label(name); |
| 978 | } | 1010 | } |
| 979 | } | 1011 | } |
| 980 | | 1012 | |
| 981 | fn font_face(state: &mut State, options: &mut Options) { | 1013 | /// The face, from the font menu, and its size. |
| | 1014 | fn font_face(state: &mut State, options: &mut Options, name: &str) { |
| 982 | let State { ui, fonts, .. } = state; | 1015 | let State { ui, fonts, .. } = state; |
| 983 | let font = &mut options.default_font; | 1016 | let font = &mut options.default_font; |
| 984 | let combo = ui.id("combo"); | 1017 | let combo = ui.id("combo"); |
| 985 | ui::shell::combo(ui, "combo", "Font", &font.face, 200.0, self::fonts(), true); | 1018 | ui::shell::combo(ui, "combo", name, &font.face, 200.0, self::fonts(), true); |
| 986 | let items: Vec<_> = fonts | 1019 | let items: Vec<_> = fonts |
| 987 | .iter() | 1020 | .iter() |
| 988 | .map(|name| Item { | 1021 | .map(|face| Item { |
| 989 | text: name, | 1022 | text: face, |
| 990 | font: Some(name), | 1023 | font: Some(face), |
| 991 | current: *name == font.face, | 1024 | current: *face == font.face, |
| 992 | ..Item::default() | 1025 | ..Item::default() |
| 993 | }) | 1026 | }) |
| 994 | .collect(); | 1027 | .collect(); |
| 995 | let anchor = Anchor::Over(ui.rect(combo).unwrap_or_default()); | 1028 | let anchor = Anchor::Over(ui.rect(combo).unwrap_or_default()); |
| 996 | if let Some(index) = ui::popup::menu(ui, self::fonts(), anchor, &items, Some("Font")) { | 1029 | if let Some(index) = ui::popup::menu(ui, self::fonts(), anchor, &items, Some(name)) { |
| 997 | font.face = fonts[index].clone(); | 1030 | font.face = fonts[index].clone(); |
| 998 | } | 1031 | } |
| 999 | } | | |
| 1000 | | | |
| 1001 | fn font_size(state: &mut State, options: &mut Options) { | | |
| 1002 | let ui = &mut state.ui; | | |
| 1003 | let font = &mut options.default_font; | | |
| 1004 | let combo = ui.id("combo"); | | |
| 1005 | let size = format!("{}", font.size); | | |
| 1006 | ui::shell::combo(ui, "combo", "Size", &size, 60.0, sizes(), true); | | |
| 1007 | let labels = SIZES.map(|size| format!("{size}")); | 1032 | let labels = SIZES.map(|size| format!("{size}")); |
| 1008 | let items: Vec<_> = labels | 1033 | let shown = format!("{}", font.size); |
| 1009 | .iter() | 1034 | let mut choices: Vec<&str> = labels.iter().map(String::as_str).collect(); |
| 1010 | .map(|label| Item { | 1035 | // A size typed in the toolbar's box may be none of OneNote's. |
| 1011 | text: label, | 1036 | let chosen = SIZES.iter().position(|size| *size == font.size); |
| 1012 | checked: Some(*label == size), | 1037 | let chosen = chosen.unwrap_or_else(|| { |
| 1013 | current: *label == size, | 1038 | choices.push(&shown); |
| 1014 | ..Item::default() | 1039 | choices.len() - 1 |
| 1015 | }) | 1040 | }); |
| 1016 | .collect(); | 1041 | ui.open( |
| 1017 | let anchor = Anchor::Over(ui.rect(combo).unwrap_or_default()); | 1042 | "size", |
| 1018 | if let Some(index) = ui::popup::menu(ui, sizes(), anchor, &items, None) { | 1043 | Spec { |
| 1019 | font.size = SIZES[index]; | 1044 | size: [ui::children(), ui::children()], |
| | 1045 | ..Spec::default() |
| | 1046 | }, |
| | 1047 | ); |
| | 1048 | if let Some(index) = dropdown(ui, "Size", &choices, chosen, 64.0) { |
| | 1049 | font.size = SIZES.get(index).copied().unwrap_or(font.size); |
| 1020 | } | 1050 | } |
| | 1051 | ui.close(); |
| 1021 | } | 1052 | } |
| 1022 | | 1053 | |
| 1023 | fn font_color(state: &mut State, options: &mut Options) { | 1054 | fn font_color(state: &mut State, options: &mut Options, name: &str) { |
| 1024 | let ui = &mut state.ui; | 1055 | let ui = &mut state.ui; |
| 1025 | let font = &mut options.default_font; | 1056 | let font = &mut options.default_font; |
| 1026 | let combo = ui.id("combo"); | 1057 | let combo = ui.id("combo"); |
| ... | @@ -1028,7 +1059,7 @@ fn font_color(state: &mut State, options: &mut Options) { | ... | @@ -1028,7 +1059,7 @@ fn font_color(state: &mut State, options: &mut Options) { |
| 1028 | .iter() | 1059 | .iter() |
| 1029 | .find(|(color, _)| Some(*color) == font.color) | 1060 | .find(|(color, _)| Some(*color) == font.color) |
| 1030 | .map_or("Automatic", |(_, name)| name); | 1061 | .map_or("Automatic", |(_, name)| name); |
| 1031 | ui::shell::combo(ui, "combo", "Font color", shown, 200.0, font_colors(), true); | 1062 | ui::shell::combo(ui, "combo", name, shown, 200.0, font_colors(), true); |
| 1032 | let swatches: Vec<_> = crate::FONT_COLORS | 1063 | let swatches: Vec<_> = crate::FONT_COLORS |
| 1033 | .iter() | 1064 | .iter() |
| 1034 | .map(|&(color, name)| (canvas::gpu::colorref(color), name)) | 1065 | .map(|&(color, name)| (canvas::gpu::colorref(color), name)) |
| ... | @@ -1042,31 +1073,33 @@ fn font_color(state: &mut State, options: &mut Options) { | ... | @@ -1042,31 +1073,33 @@ fn font_color(state: &mut State, options: &mut Options) { |
| 1042 | } | 1073 | } |
| 1043 | } | 1074 | } |
| 1044 | | 1075 | |
| 1045 | /// A path shown dimmed and cut to the row. | 1076 | /// Text shown dimmed and cut to the row, as a path. |
| 1046 | fn path(ui: &mut Ui, path: &std::path::Path) { | 1077 | #[cfg(not(target_arch = "wasm32"))] |
| | 1078 | fn dim(ui: &mut Ui, text: &str) { |
| 1047 | let spec = Spec { | 1079 | let spec = Spec { |
| 1048 | flags: Flags::CLIP, | 1080 | flags: Flags::CLIP, |
| 1049 | size: [fill(), px(row_height(&ui.theme))], | 1081 | size: [fill(), px(row_height(&ui.theme))], |
| 1050 | text: Some(&path.to_string_lossy()), | 1082 | text: Some(text), |
| 1051 | color: Some(ui.theme.text_dim), | 1083 | color: Some(ui.theme.text_dim), |
| 1052 | ..Spec::default() | 1084 | ..Spec::default() |
| 1053 | }; | 1085 | }; |
| 1054 | ui.leaf("path", spec); | 1086 | ui.leaf("dim", spec); |
| 1055 | } | 1087 | } |
| 1056 | | 1088 | |
| 1057 | fn cache(state: &mut State, _: &mut Options) { | 1089 | #[cfg(not(target_arch = "wasm32"))] |
| 1058 | path(&mut state.ui, &state.cache); | 1090 | fn cache(state: &mut State, _: &mut Options, _: &str) { |
| | 1091 | dim(&mut state.ui, &state.cache.to_string_lossy()); |
| 1059 | let label = if cfg!(target_os = "macos") { | 1092 | let label = if cfg!(target_os = "macos") { |
| 1060 | "Show in Finder" | 1093 | "Show in Finder" |
| 1061 | } else { | 1094 | } else { |
| 1062 | "Open Folder" | 1095 | "Open Folder" |
| 1063 | }; | 1096 | }; |
| 1064 | if ui::button(&mut state.ui, "reveal", label).clicked { | 1097 | if ui::button(&mut state.ui, "reveal", label).clicked { |
| 1065 | platform::reveal(&state.cache); | 1098 | crate::platform::reveal(&state.cache); |
| 1066 | } | 1099 | } |
| 1067 | } | 1100 | } |
| 1068 | | 1101 | |
| 1069 | fn version(state: &mut State, _: &mut Options) { | 1102 | fn version(state: &mut State, _: &mut Options, _: &str) { |
| 1070 | let ui = &mut state.ui; | 1103 | let ui = &mut state.ui; |
| 1071 | let spec = Spec { | 1104 | let spec = Spec { |
| 1072 | size: [fill(), px(row_height(&ui.theme))], | 1105 | size: [fill(), px(row_height(&ui.theme))], |
| ... | @@ -1078,20 +1111,25 @@ fn version(state: &mut State, _: &mut Options) { | ... | @@ -1078,20 +1111,25 @@ fn version(state: &mut State, _: &mut Options) { |
| 1078 | | 1111 | |
| 1079 | /// Where Install put Snowbound and Uninstall, or Install where it isn't in the app menu. | 1112 | /// Where Install put Snowbound and Uninstall, or Install where it isn't in the app menu. |
| 1080 | #[cfg(target_os = "linux")] | 1113 | #[cfg(target_os = "linux")] |
| 1081 | fn installed(state: &mut State, _: &mut Options) { | 1114 | fn installed(state: &mut State, _: &mut Options, _: &str) { |
| 1082 | let ui = &mut state.ui; | 1115 | let ui = &mut state.ui; |
| 1083 | if crate::desktop::uninstallable() { | 1116 | if crate::desktop::uninstallable() { |
| 1084 | path(ui, &crate::desktop::binary().unwrap_or_default()); | 1117 | dim( |
| | 1118 | ui, |
| | 1119 | &crate::desktop::binary() |
| | 1120 | .unwrap_or_default() |
| | 1121 | .to_string_lossy(), |
| | 1122 | ); |
| 1085 | if ui::button(ui, "uninstall", "Uninstall").clicked { | 1123 | if ui::button(ui, "uninstall", "Uninstall").clicked { |
| 1086 | crate::desktop::uninstall(&state.proxy); | 1124 | crate::desktop::uninstall(&state.proxy); |
| 1087 | } | 1125 | } |
| 1088 | } else if crate::desktop::installable() { | 1126 | } else if crate::desktop::installable() { |
| 1089 | path(ui, std::path::Path::new("Not in the app menu")); | 1127 | dim(ui, "Not in the app menu"); |
| 1090 | if ui::button(ui, "install", "Install").clicked { | 1128 | if ui::button(ui, "install", "Install").clicked { |
| 1091 | crate::desktop::install(); | 1129 | crate::desktop::install(); |
| 1092 | } | 1130 | } |
| 1093 | } else { | 1131 | } else { |
| 1094 | path(ui, std::path::Path::new("By your system's package manager")); | 1132 | dim(ui, "By your system's package manager"); |
| 1095 | } | 1133 | } |
| 1096 | } | 1134 | } |
| 1097 | | 1135 | |