Created
January 31, 2025 02:43
-
-
Save airstrike/be012bfb5fd4f80ac6d837531ca94a40 to your computer and use it in GitHub Desktop.
iced font sampler
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use iced::Alignment::Center; | |
use iced::Length::Fill; | |
use iced::font::{self, Font}; | |
use iced::widget::{column, horizontal_rule, pick_list, row, scrollable, text, text_input}; | |
use iced::{Element, Size, Task}; | |
use std::collections::HashMap; | |
// Font candidates with their names and possible paths | |
const FONT_CANDIDATES: &[(&str, &[&str])] = &[ | |
("Menlo", &[ | |
"/System/Library/Fonts/Menlo.ttc", | |
"/Library/Fonts/Menlo.ttc", | |
]), | |
("JuliaMono", &[ | |
"/Library/Fonts/JuliaMono-Regular.ttf", | |
"/Library/Fonts/JuliaMono-RegularLatin.ttf", | |
]), | |
("Cascadia Code", &[ | |
"/Library/Fonts/CascadiaCode.ttf", | |
"/Library/Fonts/CascadiaCodePL.ttf", | |
]), | |
("Monaco", &["/System/Library/Fonts/Monaco.ttf"]), | |
("Courier", &["/System/Library/Fonts/Courier.ttc"]), | |
]; | |
#[derive(Debug, Clone)] | |
enum Message { | |
Input(String), | |
FontLoaded(&'static str, Result<(), font::Error>), | |
FontSelected(FontFamily), | |
} | |
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] | |
enum FontFamily { | |
DefaultMonospace, | |
Custom(&'static str), | |
} | |
impl FontFamily { | |
fn to_font(&self) -> Font { | |
match self { | |
FontFamily::DefaultMonospace => Font::MONOSPACE, | |
FontFamily::Custom(name) => Font::with_name(name), | |
} | |
} | |
} | |
impl std::fmt::Display for FontFamily { | |
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | |
match self { | |
FontFamily::DefaultMonospace => write!(f, "System Monospace"), | |
FontFamily::Custom(name) => write!(f, "{}", name), | |
} | |
} | |
} | |
struct App { | |
input: String, | |
selected_font: FontFamily, | |
available_fonts: Vec<FontFamily>, | |
font_load_status: HashMap<&'static str, bool>, | |
} | |
impl App { | |
fn new() -> (Self, Task<Message>) { | |
let mut app = Self { | |
input: "Centered. Amazingly few discotheques provide jukeboxes.".to_string(), | |
selected_font: FontFamily::DefaultMonospace, | |
available_fonts: vec![FontFamily::DefaultMonospace], | |
font_load_status: HashMap::new(), | |
}; | |
// Create tasks for loading each font | |
let mut font_tasks = Vec::new(); | |
if let Ok(home) = std::env::var("HOME") { | |
for &(name, paths) in FONT_CANDIDATES { | |
// Try user's font directory first | |
let user_path = format!( | |
"{}/Library/Fonts/{}", | |
home, | |
paths[0].split('/').last().unwrap_or_default() | |
); | |
if let Ok(bytes) = std::fs::read(&user_path) { | |
font_tasks.push( | |
font::load(bytes).map(move |result| Message::FontLoaded(name, result)), | |
); | |
continue; | |
} | |
// Try system paths if user path failed | |
for &path in paths { | |
if let Ok(bytes) = std::fs::read(path) { | |
font_tasks.push( | |
font::load(bytes).map(move |result| Message::FontLoaded(name, result)), | |
); | |
break; | |
} | |
} | |
} | |
} | |
// Initialize font status as pending | |
for &(name, _) in FONT_CANDIDATES { | |
app.font_load_status.insert(name, false); | |
} | |
(app, Task::batch(font_tasks)) | |
} | |
fn theme(&self) -> iced::Theme { | |
iced::Theme::Light | |
} | |
fn update(&mut self, message: Message) -> Task<Message> { | |
match message { | |
Message::Input(value) => { | |
self.input = value; | |
} | |
Message::FontLoaded(name, Ok(())) => { | |
println!("Font loaded successfully: {}", name); | |
self.font_load_status.insert(name, true); | |
self.available_fonts.push(FontFamily::Custom(name)); | |
} | |
Message::FontLoaded(name, Err(e)) => { | |
eprintln!("Failed to load font {} from paths:", name); | |
if let Some(paths) = FONT_CANDIDATES | |
.iter() | |
.find(|(n, _)| *n == name) | |
.map(|(_, p)| p) | |
{ | |
for path in *paths { | |
eprintln!( | |
" {}: {}", | |
path, | |
if std::path::Path::new(path).exists() { | |
"exists" | |
} else { | |
"not found" | |
} | |
); | |
} | |
} | |
eprintln!("Error: {:?}", e); | |
self.font_load_status.insert(name, false); | |
} | |
Message::FontSelected(font) => { | |
self.selected_font = font; | |
} | |
} | |
Task::none() | |
} | |
fn view(&self) -> Element<Message> { | |
let samples = (8..=24) | |
.map(|size| { | |
column![ | |
row![ | |
text(format!("Size: {}", size)).size(16).width(60), | |
text(&self.input) | |
.size(size) | |
.font(self.selected_font.to_font()) | |
] | |
.height(30) | |
.align_y(Center) | |
.spacing(5), | |
horizontal_rule(1) | |
] | |
.into() | |
}) | |
.collect::<Vec<_>>(); | |
let font_picker = pick_list( | |
&self.available_fonts[..], | |
Some(self.selected_font), | |
Message::FontSelected, | |
) | |
.width(Fill); | |
let status_text = self | |
.font_load_status | |
.iter() | |
.filter(|&(_, &loaded)| loaded) | |
.count() | |
+ 1; | |
column![ | |
row![ | |
text(format!( | |
"Loaded Fonts ({}/{}): ", | |
status_text, | |
FONT_CANDIDATES.len() + 1 | |
)) | |
.width(Fill), | |
font_picker, | |
] | |
.align_y(Center), | |
text_input("", &self.input) | |
.on_input(Message::Input) | |
.padding(5), | |
scrollable(column(samples).spacing(5)), | |
] | |
.padding(20) | |
.spacing(20) | |
.into() | |
} | |
} | |
fn main() -> iced::Result { | |
iced::application("iced • font sampler", App::update, App::view) | |
.theme(App::theme) | |
.window_size(Size::new(900.0, 700.0)) | |
.centered() | |
.run_with(App::new) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment