mod async_task;
mod context;
mod core;
mod feature;
mod floating_windows;
mod registry;
mod settings;
mod types;
pub use async_task::AsyncTask;
pub use context::ControllerContext;
pub use core::{
CONTROLLER_ICON_SIZE, format_time_ago, load_default_background, render_navigation,
render_title_bar,
};
pub use feature::{ControllerFeature, FeatureState};
pub use registry::FeatureRegistry;
pub use types::{TabId, TabInfo};
use crate::gui::content::Content;
use crate::gui::window::{CommandSender, WindowRegistry};
use egui::TextureHandle;
use std::collections::HashMap;
pub struct ControllerState {
command_sender: CommandSender,
window_registry: WindowRegistry,
icon_cache: HashMap<String, TextureHandle>,
controller_background: Option<Content>,
features: FeatureRegistry,
initialized: bool,
}
impl ControllerState {
pub fn new(command_sender: CommandSender, window_registry: WindowRegistry) -> Self {
let features = create_default_registry();
Self {
command_sender,
window_registry,
icon_cache: HashMap::new(),
controller_background: load_default_background(),
features,
initialized: false,
}
}
pub fn registry(&self) -> &WindowRegistry {
&self.window_registry
}
pub fn registry_mut(&mut self) -> &mut WindowRegistry {
&mut self.window_registry
}
pub fn background(&self) -> Option<&Content> {
self.controller_background.as_ref()
}
fn ensure_initialized(&mut self, egui_ctx: &egui::Context) {
if self.initialized {
return;
}
let mut ctx = ControllerContext {
command_sender: &self.command_sender,
registry: &mut self.window_registry,
icon_cache: &mut self.icon_cache,
egui_ctx,
controller_background: &mut self.controller_background,
};
if let Err(e) = self.features.initialize_all(&mut ctx) {
log::error!("Failed to initialize features: {}", e);
}
self.features.activate_first(&mut ctx);
self.initialized = true;
}
pub fn render(&mut self, ctx: &egui::Context) {
self.ensure_initialized(ctx);
if let Some(Content::Image { data, width, height, .. }) = &self.controller_background {
#[allow(deprecated)]
let screen_rect = ctx.input(|i| i.screen_rect());
let texture = ctx.load_texture(
"controller_bg",
egui::ColorImage::from_rgba_unmultiplied([*width as usize, *height as usize], data),
egui::TextureOptions::LINEAR,
);
let painter = ctx.layer_painter(egui::LayerId::background());
painter.image(
texture.id(),
screen_rect,
egui::Rect::from_min_max(egui::pos2(0.0, 0.0), egui::pos2(1.0, 1.0)),
egui::Color32::WHITE,
);
}
let title_frame = egui::Frame::new()
.fill(egui::Color32::from_rgba_unmultiplied(20, 20, 25, 240))
.inner_margin(egui::Margin::symmetric(12, 8));
let nav_frame = egui::Frame::new()
.fill(egui::Color32::from_rgba_unmultiplied(25, 25, 30, 230))
.inner_margin(egui::Margin::symmetric(8, 4));
let content_frame = egui::Frame::new()
.fill(egui::Color32::from_rgba_unmultiplied(30, 30, 35, 220))
.inner_margin(egui::Margin::same(16));
{
let mut ctx_for_update = ControllerContext {
command_sender: &self.command_sender,
registry: &mut self.window_registry,
icon_cache: &mut self.icon_cache,
egui_ctx: ctx,
controller_background: &mut self.controller_background,
};
self.features.update_all(&mut ctx_for_update);
}
egui::TopBottomPanel::top("title_bar").frame(title_frame).show(ctx, |ui| {
let controller_ctx = ControllerContext {
command_sender: &self.command_sender,
registry: &mut self.window_registry,
icon_cache: &mut self.icon_cache,
egui_ctx: ctx,
controller_background: &mut self.controller_background,
};
render_title_bar(ui, &controller_ctx);
});
egui::SidePanel::left("navigation")
.resizable(false)
.exact_width(160.0)
.frame(nav_frame)
.show(ctx, |ui| {
let mut controller_ctx = ControllerContext {
command_sender: &self.command_sender,
registry: &mut self.window_registry,
icon_cache: &mut self.icon_cache,
egui_ctx: ctx,
controller_background: &mut self.controller_background,
};
render_navigation(ui, &mut self.features, &mut controller_ctx);
});
egui::CentralPanel::default().frame(content_frame).show(ctx, |ui| {
egui::ScrollArea::vertical().auto_shrink([false; 2]).show(ui, |ui| {
let mut controller_ctx = ControllerContext {
command_sender: &self.command_sender,
registry: &mut self.window_registry,
icon_cache: &mut self.icon_cache,
egui_ctx: ctx,
controller_background: &mut self.controller_background,
};
self.features.render_active(ui, &mut controller_ctx);
});
});
}
pub fn shutdown(&mut self) {
self.features.shutdown_all();
}
}
pub fn create_default_registry() -> FeatureRegistry {
let mut registry = FeatureRegistry::new();
registry.register(Box::new(floating_windows::FloatingWindowsFeature::new()));
registry.register(Box::new(settings::SettingsFeature::new()));
registry.register(Box::new(crate::screenshot::ScreenshotFeature::new()));
registry.register(Box::new(crate::gui::menu_bar::MenuBarFeature::new()));
registry.register(Box::new(crate::clipboard_manager::ClipboardFeature::new()));
#[cfg(feature = "stt")]
registry.register(Box::new(crate::stt::SttFeature::new()));
#[cfg(feature = "ocr")]
registry.register(Box::new(crate::ocr::OcrFeature::new()));
#[cfg(feature = "image_match")]
registry.register(Box::new(crate::image_match::ImageMatchFeature::new()));
#[cfg(all(feature = "click_helper", target_os = "macos"))]
registry.register(Box::new(crate::click_helper::ClickHelperFeature::new()));
registry
}