use crate::Embedded;
use crate::prelude::*;
use fontdue::Font;
use std::path::PathBuf;
use std::sync::mpsc::{Receiver, Sender};
#[cfg(not(target_arch = "wasm32"))]
use std::sync::mpsc::{self};
pub struct TheFileExtension {
pub name: String,
pub extensions: Vec<String>,
}
impl TheFileExtension {
pub fn new(name: String, extensions: Vec<String>) -> Self {
Self { name, extensions }
}
}
pub struct TheUIContext {
pub font: Option<Font>,
icons: FxHashMap<String, TheRGBABuffer>,
pub focus: Option<TheId>,
pub keyboard_focus: Option<TheId>,
pub hover: Option<TheId>,
pub overlay: Option<TheId>,
pub context_menu: Option<TheContextMenu>,
pub disabled_ids: FxHashSet<String>,
pub state_events_sender: Option<Sender<TheEvent>>,
pub redraw_all: bool,
pub relayout: bool,
pub undo_stack: TheUndoStack,
pub drop: Option<TheDrop>,
pub file_requester_receiver: Option<(TheId, Receiver<Vec<PathBuf>>)>,
pub accelerators: FxHashMap<TheId, TheAccelerator>,
pub clipboard: Option<TheValue>,
pub clipboard_app_type: Option<String>,
}
impl Default for TheUIContext {
fn default() -> Self {
Self::new()
}
}
impl TheUIContext {
pub fn new() -> Self {
let mut font: Option<Font> = None;
let mut icons: FxHashMap<String, TheRGBABuffer> = FxHashMap::default();
for file in Embedded::iter() {
let name = file.as_ref();
if name.starts_with("fonts/Roboto-Bold") {
if let Some(font_bytes) = Embedded::get(name) {
if let Ok(f) =
Font::from_bytes(font_bytes.data, fontdue::FontSettings::default())
{
font = Some(f);
}
}
} else if name.starts_with("icons/") {
if let Some(file) = Embedded::get(name) {
let data = std::io::Cursor::new(file.data);
let decoder = png::Decoder::new(data);
if let Ok(mut reader) = decoder.read_info() {
let mut buf = vec![0; reader.output_buffer_size()];
let info = reader.next_frame(&mut buf).unwrap();
let bytes = &buf[..info.buffer_size()];
let rgba_bytes = if info.color_type.samples() == 3 {
let mut expanded_buf =
Vec::with_capacity(info.width as usize * info.height as usize * 4);
for chunk in bytes.chunks(3) {
expanded_buf.push(chunk[0]); expanded_buf.push(chunk[1]); expanded_buf.push(chunk[2]); expanded_buf.push(255); }
expanded_buf
} else {
bytes.to_vec()
};
let mut cut_name = name.replace("icons/", "");
cut_name = cut_name.replace(".png", "");
icons.insert(
cut_name.to_string(),
TheRGBABuffer::from(rgba_bytes, info.width, info.height),
);
}
}
}
}
Self {
focus: None,
keyboard_focus: None,
hover: None,
overlay: None,
context_menu: None,
font,
icons,
disabled_ids: FxHashSet::default(),
state_events_sender: None,
redraw_all: false,
relayout: false,
undo_stack: TheUndoStack::default(),
drop: None,
file_requester_receiver: None,
accelerators: FxHashMap::default(),
clipboard: None,
clipboard_app_type: None,
}
}
pub fn set_disabled(&mut self, id: &str) {
self.disabled_ids.insert(id.to_string());
self.set_widget_state(id.to_string(), TheWidgetState::None);
}
pub fn is_disabled(&self, id: &str) -> bool {
self.disabled_ids.contains(id)
}
pub fn set_enabled(&mut self, id: &str) {
self.disabled_ids.remove(id);
self.set_widget_state(id.to_string(), TheWidgetState::None);
}
pub fn add_icon(&mut self, name: String, icon: TheRGBABuffer) {
self.icons.insert(name, icon);
}
pub fn icon(&self, name: &str) -> Option<&TheRGBABuffer> {
if let Some(icon) = self.icons.get(name) {
return Some(icon);
}
None
}
pub fn set_focus(&mut self, id: &TheId) {
if !id.equals(&self.focus) {
if let Some(focus) = &self.focus {
self.send(TheEvent::LostFocus(focus.clone()));
}
self.send(TheEvent::GainedFocus(id.clone()));
self.focus = Some(id.clone());
}
}
pub fn clear_focus(&mut self) {
self.focus = None;
}
pub fn has_focus(&self, id: &TheId) -> bool {
id.equals(&self.focus)
}
pub fn set_hover(&mut self, id: &TheId) {
if !id.equals(&self.hover) {
if let Some(hover) = &self.hover {
self.send(TheEvent::LostHover(hover.clone()));
}
self.send(TheEvent::GainedHover(id.clone()));
self.hover = Some(id.clone());
}
}
pub fn clear_hover(&mut self) {
self.hover = None;
}
pub fn set_overlay(&mut self, id: &TheId) {
self.overlay = Some(id.clone());
}
pub fn clear_overlay(&mut self) {
self.overlay = None;
self.redraw_all = true;
}
pub fn set_drop(&mut self, drop: TheDrop) {
self.drop = Some(drop);
}
pub fn clear_drop(&mut self) {
self.drop = None;
}
pub fn has_drop(&self) -> bool {
self.drop.is_some()
}
pub fn send_widget_state_changed(&mut self, id: &TheId, state: TheWidgetState) {
self.send(TheEvent::StateChanged(id.clone(), state));
}
pub fn set_widget_state(&mut self, name: String, state: TheWidgetState) {
self.send(TheEvent::SetState(name, state));
}
pub fn set_widget_state_id(&mut self, id: Uuid, state: TheWidgetState) {
self.send(TheEvent::SetStateId(id, state));
}
pub fn send(&mut self, event: TheEvent) {
if let Some(sender) = &mut self.state_events_sender {
sender.send(event).unwrap();
}
}
pub fn send_widget_value_changed(&mut self, id: &TheId, value: TheValue) {
self.send(TheEvent::ValueChanged(id.clone(), value));
}
#[cfg(not(target_arch = "wasm32"))]
pub fn open_file_requester(&mut self, id: TheId, title: String, extension: TheFileExtension) {
let (tx, rx): (Sender<Vec<PathBuf>>, Receiver<Vec<PathBuf>>) = mpsc::channel();
let task = rfd::AsyncFileDialog::new()
.add_filter(extension.name, &extension.extensions)
.set_title(title)
.pick_files();
std::thread::spawn(move || {
let files = futures::executor::block_on(task);
if let Some(files) = files {
let mut ff = vec![];
for f in files {
ff.push(f.path().to_path_buf());
}
tx.send(ff).unwrap();
} else {
tx.send(vec![]).unwrap();
}
});
self.file_requester_receiver = Some((id, rx));
}
#[cfg(not(target_arch = "wasm32"))]
pub fn save_file_requester(&mut self, id: TheId, title: String, extension: TheFileExtension) {
let (tx, rx): (Sender<Vec<PathBuf>>, Receiver<Vec<PathBuf>>) = mpsc::channel();
let task = rfd::AsyncFileDialog::new()
.add_filter(extension.name, &extension.extensions)
.set_title(title)
.save_file();
std::thread::spawn(move || {
let file = futures::executor::block_on(task);
if let Some(file) = file {
let ff = vec![file.path().to_path_buf()];
tx.send(ff).unwrap();
} else {
tx.send(vec![]).unwrap();
}
});
self.file_requester_receiver = Some((id, rx));
}
pub fn decode_image(&mut self, id: TheId, path: PathBuf) {
match image::open(&path) {
Ok(img) => {
let rgba = img.to_rgba8();
let (width, height) = rgba.dimensions();
let buffer = TheRGBABuffer::from(rgba.into_vec(), width, height);
let name = path.file_stem().and_then(|f| f.to_str()).unwrap_or("");
self.send(TheEvent::ImageDecodeResult(id, name.to_string(), buffer));
}
Err(err) => {
eprintln!("Failed to decode image {}: {}", path.to_string_lossy(), err);
}
}
}
}