spectral / src /main.rs
stevenkhan's picture
Upload src/main.rs
4729538 verified
use std::sync::Arc;
use std::time::Instant;
use log::info;
use winit::application::ApplicationHandler;
use winit::event::WindowEvent;
use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop};
use winit::window::{Window, WindowId};
use spectral_core::{Config, Terminal};
use spectral_font::{Font, FontFamily, FontStyle, GlyphKey};
use spectral_render::Renderer;
struct SpectralApp {
window: Option<Arc<Window>>,
renderer: Option<Renderer>,
terminal: Terminal,
config: Config,
font_family: Option<FontFamily>,
last_render: Instant,
}
impl SpectralApp {
fn new(config: Config) -> Self {
let terminal = Terminal::new(config.clone());
Self {
window: None,
renderer: None,
terminal,
config,
font_family: None,
last_render: Instant::now(),
}
}
fn init_window(&mut self, event_loop: &ActiveEventLoop) {
let window_attrs = Window::default_attributes()
.with_title("Spectral Terminal")
.with_inner_size(winit::dpi::LogicalSize::new(800, 600));
let window = Arc::new(
event_loop.create_window(window_attrs).expect("Failed to create window"),
);
let rt = tokio::runtime::Runtime::new().unwrap();
let renderer = rt.block_on(Renderer::new(
window.clone(),
self.terminal.grid.cols as u32,
self.terminal.grid.rows as u32,
2048,
));
self.window = Some(window);
self.renderer = Some(renderer);
self.load_fonts();
}
fn load_fonts(&mut self) {
let font_names = [
&*self.config.font_family,
"Iosevka Extended", "Iosevka", "JetBrains Mono", "Fira Code", "monospace",
];
for &name in &font_names {
if let Some(font_data) = try_load_font(name) {
let mut family = FontFamily::new(name);
if let Some(font) = Font::from_bytes(0, font_data, 0, FontStyle::Regular) {
family.add_font(font);
self.font_family = Some(family);
info!("Loaded font: {}", name);
break;
}
}
}
if let Some(renderer) = &mut self.renderer {
if let Some(family) = &self.font_family {
if let Some(font) = family.get_font(FontStyle::Regular) {
let px = self.config.font_size;
for ch in (32u8..=126).map(|b| b as char) {
let glyph_id = font.lookup_glyph(ch);
let (metrics, bitmap) = font.rasterize_glyph(glyph_id, px);
let w = metrics.width as u16;
let h = metrics.height as u16;
if w > 0 && h > 0 {
let key = GlyphKey {
font_id: font.id, glyph_id,
size_bucket: (px * 4.0) as u16,
style_flags: 0,
};
let rgba = spectral_font::msdf::r8_to_rgba(&bitmap);
renderer.glyph_atlas.insert(key, w, h, &rgba);
}
}
renderer.needs_atlas_upload = true;
}
}
}
}
fn handle_input(&mut self, text: &str) {
let bytes = text.as_bytes();
self.terminal.feed(bytes);
self.request_redraw();
}
fn request_redraw(&mut self) {
if let Some(window) = &self.window {
window.request_redraw();
}
}
fn render(&mut self) {
if let Some(renderer) = &mut self.renderer {
renderer.update_atlas();
renderer.update_instances(&self.terminal);
renderer.render();
}
self.last_render = Instant::now();
}
}
fn try_load_font(name: &str) -> Option<Arc<Vec<u8>>> {
let paths = [
format!("/usr/share/fonts/truetype/{}/{}.ttf", name.to_lowercase().replace(' ', ""), name),
format!("/usr/share/fonts/TTF/{}.ttf", name.replace(' ', "")),
format!("/usr/share/fonts/truetype/{}.ttf", name.replace(' ', "")),
format!("/usr/share/fonts/{}.ttf", name),
format!("/usr/share/fonts/truetype/{}.ttf", name.to_lowercase()),
format!("/usr/share/fonts/truetype/iosevka/{}-Regular.ttf", name.replace(' ', "")),
format!("/usr/share/fonts/truetype/iosevka/{}-Regular.ttf", name.replace(" Extended", "")),
];
for path in &paths {
if let Ok(data) = std::fs::read(path) {
return Some(Arc::new(data));
}
}
if let Ok(output) = std::process::Command::new("fc-match")
.args(["-f", "%{file}", name]).output()
{
let path = String::from_utf8_lossy(&output.stdout);
let path = path.trim();
if !path.is_empty() && path != name {
if let Ok(data) = std::fs::read(path) {
return Some(Arc::new(data));
}
}
}
None
}
impl ApplicationHandler for SpectralApp {
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
if self.window.is_none() { self.init_window(event_loop); }
}
fn window_event(&mut self, event_loop: &ActiveEventLoop, _window_id: WindowId, event: WindowEvent) {
match event {
WindowEvent::CloseRequested => event_loop.exit(),
WindowEvent::Resized(size) => {
if let Some(renderer) = &mut self.renderer {
renderer.resize(size.width, size.height);
}
}
WindowEvent::KeyboardInput { event, .. } => {
if event.state == winit::event::ElementState::Pressed {
if let Some(text) = event.text {
self.handle_input(&text);
}
}
}
WindowEvent::RedrawRequested => { self.render(); }
_ => {}
}
}
fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) {}
}
fn main() {
env_logger::init();
let event_loop = EventLoop::new().unwrap();
event_loop.set_control_flow(ControlFlow::Poll);
let config = Config::default();
let mut app = SpectralApp::new(config);
event_loop.run_app(&mut app).unwrap();
}