diff --git a/src/Tools/tim_tool/Cargo.lock b/src/Tools/tim_tool/Cargo.lock index 24e46b9e..b265ea81 100644 --- a/src/Tools/tim_tool/Cargo.lock +++ b/src/Tools/tim_tool/Cargo.lock @@ -3677,9 +3677,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.137" +version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "930cfb6e6abf99298aaad7d29abbef7a9999a9a8806a40088f55f0dcec03146b" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" dependencies = [ "itoa", "memchr", @@ -4113,6 +4113,8 @@ version = "0.1.0" dependencies = [ "png", "rfd", + "serde", + "serde_json", "slint", "slint-build", "tiny-skia", diff --git a/src/Tools/tim_tool/Cargo.toml b/src/Tools/tim_tool/Cargo.toml index a5253132..a9b3f0d2 100644 --- a/src/Tools/tim_tool/Cargo.toml +++ b/src/Tools/tim_tool/Cargo.toml @@ -11,6 +11,8 @@ png = "0.17.16" rfd = "0.15.2" slint = "1.9.2" tiny-skia = "0.11.4" +serde = {version = "1.0.140", features = ["derive"]} +serde_json = "1.0" tool_helper = {path = "../tool_helper"} [build-dependencies] diff --git a/src/Tools/tim_tool/src/gui/file_tab/callbacks.rs b/src/Tools/tim_tool/src/gui/file_tab/callbacks.rs index f1d11052..8c5bedd6 100644 --- a/src/Tools/tim_tool/src/gui/file_tab/callbacks.rs +++ b/src/Tools/tim_tool/src/gui/file_tab/callbacks.rs @@ -1,8 +1,10 @@ -use crate::{gui::{main_tab::MainTab, MutexTIMManager, VRAM_HEIGHT, VRAM_WIDTH}, MainWindow}; +use std::{fs::File, io::Write, path::PathBuf}; + +use crate::{gui::{self, main_tab::{MainTab, NewVRAMImageInfo}, MutexTIMManager, VRAM_HEIGHT, VRAM_WIDTH}, MainWindow}; use super::FileTab; use rfd::FileDialog; use slint::SharedString; -use tim_tool::logic::tim::types::Encoding; +use tim_tool::logic::{project::{ImagePosition, Job, PaletteRect, Project}, tim::types::Encoding, TIMManager}; use tool_helper::Error; pub(super) fn on_browse_file(tim_manager: MutexTIMManager) -> impl FnMut(&mut FileTab, &MainWindow) -> Result<(), Error> + 'static { @@ -32,7 +34,7 @@ pub(super) fn on_browse_file(tim_manager: MutexTIMManager) -> impl FnMut(&mut Fi }; } -pub(super) fn on_update_palette_size(tim_manager: MutexTIMManager) -> impl FnMut(&mut FileTab, &MainWindow, u32, u32) -> Result<(), Error> + 'static { +pub(super) fn on_update_palette_size(tim_manager: MutexTIMManager) -> impl FnMut(&mut FileTab, &MainWindow, u16, u16) -> Result<(), Error> + 'static { return move |file_tab, _main_window, width, height| -> Result<(), Error> { file_tab.update_palette(tim_manager.lock().expect("VRAM already locked").change_unadded_tim_palette_size(width, height)?); Ok(()) @@ -43,15 +45,8 @@ pub(super) fn on_add_image(tim_manager: MutexTIMManager) -> impl FnMut(&mut File return move |file_tab, main_tab, main_window| { let file_name = file_tab.get_file_name(); let encoding = file_tab.get_encoding()?; - let mut tim_mgr = tim_manager.lock().expect("VRAM already locked"); - let (image, palette_image) = tim_mgr.get_converted_unadded_tim_image(encoding)?; - let (full_image, _) = tim_mgr.get_converted_unadded_tim_image(Encoding::FullColor)?; - let images_created = main_tab.add_new_vram_file(&file_name, full_image, image, encoding, palette_image); - if let Err(error) = tim_mgr.add_unadded_tim(images_created) { - main_tab.pop_vram_files(images_created); - return Err(error); - } + add_unadded_tim(main_tab, &mut tim_manager.lock().expect("VRAM already locked"), UnaddedTIM::new(&file_name, encoding))?; file_tab.clear_load(); main_window.invoke_change_to_main(); @@ -59,26 +54,173 @@ pub(super) fn on_add_image(tim_manager: MutexTIMManager) -> impl FnMut(&mut File }; } -pub(super) fn on_load_project_clicked() -> impl FnMut(&mut FileTab, &MainWindow) -> Result<(), Error> + 'static { - move |_file_tab, _main_window| { - Err(Error::not_implemented("on_load_project_clicked")) +pub(super) fn on_load_project_clicked(tim_manager: MutexTIMManager) -> impl FnMut(&mut FileTab, &mut MainTab, &MainWindow) -> Result<(), Error> + 'static { + move |_file_tab, main_tab, main_window| { + let open_location = main_window.get_project_widget_open_project_path(); + if open_location.is_empty() { + return Err(Error::from_str("Please specify location for project file to open")); + } + + let file_content = tool_helper::read_file_to_string(&PathBuf::from(open_location.as_str()))?; + let new_project = match serde_json::from_str::(&file_content) { + Ok(project) => project, + Err(error) => { + return Err(Error::from_error(error)); + } + }; + + let mut tim_manager = tim_manager.lock().expect("VRAM already locked"); + + if !main_window.get_project_widget_append_project_elements() { + clear_all_vram_images(main_tab, &mut tim_manager); + main_window.invoke_clear_file_tab_current_selected_file(); + } + + for job in new_project.jobs { + let (_, _) = tim_manager.load_unadded_tim(&job.file_path)?; + let (pal_width, pal_height) = job.palette_rect.size.into(); + + tim_manager.change_unadded_tim_palette_size(pal_width, pal_height)?; + add_unadded_tim(main_tab, &mut tim_manager, UnaddedTIM::from_job(&job))?; + } + + main_window.invoke_change_to_main(); + Ok(()) } } -pub(super) fn on_save_project_clicked() -> impl FnMut(&mut FileTab, &MainWindow) -> Result<(), Error> + 'static { - move |_file_tab, _main_window| { - Err(Error::not_implemented("on_save_project_clicked")) +pub(super) fn on_save_project_clicked(tim_manager: MutexTIMManager) -> impl FnMut(&mut FileTab, &MainTab, &MainWindow) -> Result<(), Error> + 'static { + move |_file_tab, main_tab, main_window| { + let save_location = main_window.get_project_widget_save_project_path(); + if save_location.is_empty() { + return Err(Error::from_str("Please specify save location for project file")); + } + + let tim_info = tim_manager.lock().expect("VRAM already locked").clone_added_tims(); + let vram_info = main_tab.clone_vram_info(); + + if tim_info.len() != vram_info.len() { + return Err(Error::from_str("TIM and VRAM info not in sync! Please close program")); + } + + let mut cur_job:Option = None; + let mut cur_palette:Option = None; + let mut project = Project::new(tim_info.into_iter().zip(vram_info.into_iter()).filter_map(|(tim, (file_name, vram))| { + if vram.is_palette { + let cur_palette = std::mem::replace(&mut cur_palette, None); + if let Some(mut cur_palette) = cur_palette { + cur_palette.pos = ImagePosition::new(vram.x as u16, vram.y as u16); + if let Some(cur_job) = &mut cur_job { + cur_job.palette_rect = cur_palette; + } + } + None + } + + else { + // We are done with the old job + let prev_job = std::mem::replace(&mut cur_job, None); + + if let Some(tim) = tim { + if let Some((width, height)) = tim.get_palette_size() { + cur_palette = Some(PaletteRect::new(0, 0, width, height)); + } + cur_job = Some(Job::new(file_name, tim.get_path(), (vram.x as u16, vram.y as u16), Encoding::from_str(vram.encoding_str.as_str()))); + } + prev_job + } + }).collect()); + if let Some(cur_job) = cur_job { + project.jobs.push(cur_job); + } + + let json_content = match serde_json::to_string(&project) { + Ok(json_content) => json_content, + Err(error) => { + return Err(Error::from_error(error)); + } + }; + + let mut file = File::create(save_location)?; + file.write_all(json_content.as_bytes())?; + + gui::display_information("Saving project", "Project saved successfully"); + Ok(()) } } pub(super) fn on_browse_open_project_clicked() -> impl FnMut(&mut FileTab, &MainWindow) -> Result<(), Error> + 'static { - move |_file_tab, _main_window| { - Err(Error::not_implemented("on_browse_open_project_clicked")) + move |_file_tab, main_window| { + let file_path = create_project_file_dialog() + .set_title("Save location for TIM project file") + .pick_file(); + + if let Some(file_path) = file_path { + if let Some(file_path) = file_path.to_str() { + main_window.set_project_widget_open_project_path(SharedString::from(file_path)); + } + } + Ok(()) } } pub(super) fn on_browse_save_project_clicked() -> impl FnMut(&mut FileTab, &MainWindow) -> Result<(), Error> + 'static { - move |_file_tab, _main_window| { - Err(Error::not_implemented("on_browse_save_project_clicked")) + move |_file_tab, main_window| { + let file_path = create_project_file_dialog() + .set_title("Save location for TIM project file") + .save_file(); + + if let Some(file_path) = file_path { + if let Some(file_path) = file_path.to_str() { + main_window.set_project_widget_save_project_path(SharedString::from(file_path)); + } + } + Ok(()) } +} + +struct UnaddedTIM<'a> { + pub file_name: &'a String, + pub image_x: u16, + pub image_y: u16, + pub palette_x: u16, + pub palette_y: u16, + pub encoding: Encoding, +} + +impl<'a> UnaddedTIM<'a> { + pub fn new(file_name: &String, encoding: Encoding,) -> UnaddedTIM { + UnaddedTIM{file_name, image_x: 0, image_y: 0, palette_x: 0, palette_y: 0, encoding} + } + + pub fn from_job(job: &Job) -> UnaddedTIM { + UnaddedTIM{file_name: &job.name, image_x: job.image_pos.x, image_y: job.image_pos.y, palette_x: job.palette_rect.pos.x, palette_y: job.palette_rect.pos.y, encoding: job.encoding } + } +} + +fn add_unadded_tim(main_tab: &mut MainTab, tim_manager: &mut TIMManager, unadded_tim: UnaddedTIM) -> Result<(), Error> { + let (image, palette_image) = tim_manager.get_converted_unadded_tim_image(unadded_tim.encoding)?; + let (full_image, _) = tim_manager.get_converted_unadded_tim_image(Encoding::FullColor)?; + let image = NewVRAMImageInfo::new(image, unadded_tim.image_x, unadded_tim.image_y, unadded_tim.encoding); + let palette_image = if let Some(palette_image) = palette_image { Some(NewVRAMImageInfo::new(palette_image, unadded_tim.palette_x, unadded_tim.palette_y, Encoding::FullColor)) } else { None }; + + let images_created = main_tab.add_new_vram_file(unadded_tim.file_name, full_image, image, palette_image); + if let Err(error) = tim_manager.add_unadded_tim(images_created) { + main_tab.pop_vram_files(images_created); + return Err(error); + } + + Ok(()) +} + +fn clear_all_vram_images(main_tab: &mut MainTab, tim_manager: &mut TIMManager) { + tim_manager.clear(); + main_tab.clear(); +} + +fn create_project_file_dialog() -> FileDialog { + FileDialog::new() + .add_filter("TIM project file (.tim_project)", &["tim_project"]) + .add_filter("JSON file (.json; .jsn)", &["json", "jsn"]) + .add_filter("All", &["*"]) } \ No newline at end of file diff --git a/src/Tools/tim_tool/src/gui/file_tab/mod.rs b/src/Tools/tim_tool/src/gui/file_tab/mod.rs index 21f3bb4e..b5ad144c 100644 --- a/src/Tools/tim_tool/src/gui/file_tab/mod.rs +++ b/src/Tools/tim_tool/src/gui/file_tab/mod.rs @@ -60,29 +60,29 @@ impl FileTab { let (cloned_object, cloned_main_window, mut function) = (object.clone(), main_window.clone(), callbacks::on_update_palette_size(tim_manager.clone())); main_window.borrow().on_file_tab_update_palette_size(move |width, height| { - if let Err(error) = function(&mut cloned_object.borrow_mut(), &cloned_main_window.borrow(), width as u32, height as u32) { + if let Err(error) = function(&mut cloned_object.borrow_mut(), &cloned_main_window.borrow(), width as u16, height as u16) { display_error("Updating palette failed", &error.to_string()); } }); - let (cloned_object, cloned_main_window, mut function) = (object.clone(), main_window.clone(), callbacks::on_add_image(tim_manager.clone())); + let (cloned_object, cloned_main_tab, cloned_main_window, mut function) = (object.clone(), main_tab.clone(), main_window.clone(), callbacks::on_add_image(tim_manager.clone())); main_window.borrow().on_file_tab_add_convert_image(move || { - if let Err(error) = function(&mut cloned_object.borrow_mut(), &mut main_tab.borrow_mut(), &cloned_main_window.borrow()) { + if let Err(error) = function(&mut cloned_object.borrow_mut(), &mut cloned_main_tab.borrow_mut(), &cloned_main_window.borrow()) { display_error("Adding file failed", &error.to_string()); } }); // Project functions - let (cloned_object, cloned_main_window, mut function) = (object.clone(), main_window.clone(), callbacks::on_load_project_clicked()); + let (cloned_object, cloned_main_tab, cloned_main_window, mut function) = (object.clone(), main_tab.clone(), main_window.clone(), callbacks::on_load_project_clicked(tim_manager.clone())); main_window.borrow().on_project_widget_load_project_clicked(move || { - if let Err(error) = function(&mut cloned_object.borrow_mut(), &cloned_main_window.borrow()) { + if let Err(error) = function(&mut cloned_object.borrow_mut(), &mut cloned_main_tab.borrow_mut(), &cloned_main_window.borrow()) { display_error("Loading project failed", &error.to_string()); } }); - let (cloned_object, cloned_main_window, mut function) = (object.clone(), main_window.clone(), callbacks::on_save_project_clicked()); + let (cloned_object, cloned_main_tab, cloned_main_window, mut function) = (object.clone(), main_tab.clone(), main_window.clone(), callbacks::on_save_project_clicked(tim_manager.clone())); main_window.borrow().on_project_widget_save_project_clicked (move || { - if let Err(error) = function(&mut cloned_object.borrow_mut(), &cloned_main_window.borrow()) { + if let Err(error) = function(&mut cloned_object.borrow_mut(), &cloned_main_tab.borrow(), &cloned_main_window.borrow()) { display_error("Saving project failed", &error.to_string()); } }); diff --git a/src/Tools/tim_tool/src/gui/main_tab/mod.rs b/src/Tools/tim_tool/src/gui/main_tab/mod.rs index 00748d63..a4cf8087 100644 --- a/src/Tools/tim_tool/src/gui/main_tab/mod.rs +++ b/src/Tools/tim_tool/src/gui/main_tab/mod.rs @@ -1,28 +1,41 @@ mod callbacks; -use crate::{gui::{MutexTIMManager, VRAM_HEIGHT, VRAM_WIDTH}, VRAMImage}; +use crate::{gui::{MutexTIMManager, VRAM_HEIGHT, VRAM_WIDTH}, VRAMImgData, VRAMInfo, VRAMData}; use super::MainWindowRef; use slint::{Model, SharedString}; use std::{cell::RefCell, ops::RangeInclusive, rc::Rc, sync::{Arc, Mutex}}; use tim_tool::logic::tim::types::Encoding; +pub struct NewVRAMImageInfo { + pub image: slint::Image, + pub x: u16, + pub y: u16, + pub encoding: Encoding, +} + +impl NewVRAMImageInfo { + pub fn new(image: slint::Image, x: u16, y: u16, encoding: Encoding) -> NewVRAMImageInfo { + NewVRAMImageInfo{image, x, y, encoding} + } +} + struct VRAM { - count: usize, - file_list: Rc>, - image_list: Rc> + count: usize, + file_list: Rc>, + info: Rc> } impl VRAM { - pub fn push(&mut self, name: slint::StandardListViewItem, image: VRAMImage) { + pub fn push(&mut self, name: slint::StandardListViewItem, image: VRAMData) { self.file_list.push(name); - self.image_list.push(image); + self.info.push(image); self.count += 1; } pub fn remove(&mut self, idx: usize) { self.count -= 1; self.file_list.remove(idx); - self.image_list.remove(idx); + self.info.remove(idx); } pub fn pop(&mut self) { @@ -40,12 +53,12 @@ impl MainTab { pub fn new(main_window: MainWindowRef, tim_manager: MutexTIMManager) -> SharedMainTab { let vram_file_list:Vec = main_window.borrow().get_main_tab_vram_file_list().iter().collect(); let vram_file_list = Rc::new(slint::VecModel::from(vram_file_list)); - let vram_image_list = Rc::new(slint::VecModel::from(Vec::::new())); + let info = Rc::new(slint::VecModel::from(Vec::::new())); main_window.borrow().set_main_tab_vram_file_list(vram_file_list.clone().into()); - main_window.borrow().set_main_tab_vram_images(vram_image_list.clone().into()); + main_window.borrow().set_main_tab_vram_data(info.clone().into()); - let object = SharedMainTab::new(MainTab{vram: Arc::new(Mutex::new(VRAM{count: 0, file_list: vram_file_list, image_list: vram_image_list}))}.into()); + let object = SharedMainTab::new(MainTab{vram: Arc::new(Mutex::new(VRAM{count: 0, file_list: vram_file_list, info}))}.into()); let (cloned_object, cloned_main_window, mut function) = (object.clone(), main_window.clone(), callbacks::on_move_vram_image()); main_window.borrow().on_move_vram_image(move |idx, dx, dy| { @@ -59,30 +72,43 @@ impl MainTab { object } - pub fn add_new_vram_file(&mut self, file_name: &String, full_image: slint::Image, image: slint::Image, encoding: Encoding, image_palette: Option) -> usize { + pub fn clear(&mut self) { + let mut vram_data = self.vram.lock().expect("VRAM already locked"); + let count = vram_data.count; + + for _ in 0..count { + vram_data.pop(); + } + } + + pub fn add_new_vram_file(&mut self, file_name: &String, full_image: slint::Image, texture: NewVRAMImageInfo, image_palette: Option) -> usize { let mut vram_data = self.vram.lock().expect("VRAM already locked"); - let mut add_new_image = |file_name: &String, full_image: slint::Image, image: slint::Image, encoding: Encoding, palette_count: i32, is_palette: bool| { - let encoding_str = if is_palette {""} else {encoding.to_str()}; - let vram_image = VRAMImage{ - full_img: full_image, - img: image, - x: 0, - y: 0, - encoding_str: SharedString::from(encoding_str), - palette_count, - is_palette, + let mut add_new_image = |file_name: &String, full_image: slint::Image, vram_image: NewVRAMImageInfo, palette_count: i32, is_palette: bool| { + let encoding_str = if is_palette {""} else {vram_image.encoding.to_str()}; + let vram_image = VRAMData { + images: VRAMImgData { + full_image, + image: vram_image.image, + }, + info: VRAMInfo { + x: vram_image.x as i32, + y: vram_image.y as i32, + encoding_str: SharedString::from(encoding_str), + palette_count, + is_palette, + } }; vram_data.push(slint::StandardListViewItem::from(file_name.as_str()), vram_image); }; let mut images_added = 1; - add_new_image(file_name, full_image, image, encoding, if image_palette.is_some() {1} else {0}, false); + add_new_image(file_name, full_image, texture, if image_palette.is_some() {1} else {0}, false); if let Some(image_palette) = image_palette { let file_name = " => ".to_owned() + file_name.as_str() + " (Palette)"; images_added += 1; - add_new_image(&file_name, image_palette.clone(), image_palette, encoding, 0, true); + add_new_image(&file_name, image_palette.image.clone(), image_palette, 0, true); } images_added @@ -91,13 +117,13 @@ impl MainTab { pub fn remove_vram_file(&mut self, idx: usize) -> Result, &'static str> { let mut vram_data = self.vram.lock().expect("VRAM already locked"); let extras = { - if let Some(element) = vram_data.image_list.iter().skip(idx).next() { - if element.is_palette { + if let Some(element) = vram_data.info.iter().skip(idx).next() { + if element.info.is_palette { return Err("Can not remove palette. Delete image instead"); } else { - element.palette_count as usize + element.info.palette_count as usize } } @@ -126,28 +152,36 @@ impl MainTab { pub fn move_vram_image(&mut self, idx: usize, dx: i32, dy: i32) { let vram_data = self.vram.lock().expect("VRAM already locked"); - if let Some(mut vram_info) = vram_data.image_list.row_data(idx) { - vram_info.x += dx; - vram_info.y += dy; + if let Some(mut vram_info) = vram_data.info.row_data(idx) { + vram_info.info.x += dx; + vram_info.info.y += dy; - if vram_info.x < 0 { - vram_info.x = 0; + if vram_info.info.x < 0 { + vram_info.info.x = 0; } - if vram_info.y < 0 { - vram_info.y = 0; + if vram_info.info.y < 0 { + vram_info.info.y = 0; } - let (vram_img_width, vram_img_height) = (vram_info.img.size().width as i32, vram_info.img.size().height as i32); - if (vram_info.x + vram_img_width) > VRAM_WIDTH as i32 { - vram_info.x = VRAM_WIDTH as i32 - vram_img_width; + let (vram_img_width, vram_img_height) = (vram_info.images.image.size().width as i32, vram_info.images.image.size().height as i32); + if (vram_info.info.x + vram_img_width) > VRAM_WIDTH as i32 { + vram_info.info.x = VRAM_WIDTH as i32 - vram_img_width; } - if (vram_info.y + vram_img_height) > VRAM_HEIGHT as i32 { - vram_info.y = VRAM_HEIGHT as i32 - vram_img_height; + if (vram_info.info.y + vram_img_height) > VRAM_HEIGHT as i32 { + vram_info.info.y = VRAM_HEIGHT as i32 - vram_img_height; } - vram_data.image_list.set_row_data(idx, vram_info); + vram_data.info.set_row_data(idx, vram_info); } } + + pub fn clone_vram_info(&self) -> Vec<(String, VRAMInfo)> { + let vram_data = self.vram.lock().expect("VRAM already locked"); + + vram_data.info.iter().zip(vram_data.file_list.iter()).map(|(vram_data, file_name)| { + (file_name.text.to_string(), vram_data.info.clone()) + }).collect() + } } \ No newline at end of file diff --git a/src/Tools/tim_tool/src/logic/mod.rs b/src/Tools/tim_tool/src/logic/mod.rs index e5dc964f..bdb4faae 100644 --- a/src/Tools/tim_tool/src/logic/mod.rs +++ b/src/Tools/tim_tool/src/logic/mod.rs @@ -1,3 +1,4 @@ +pub mod project; pub mod tim; pub mod tim_mgr; diff --git a/src/Tools/tim_tool/src/logic/project/mod.rs b/src/Tools/tim_tool/src/logic/project/mod.rs new file mode 100644 index 00000000..32f7b726 --- /dev/null +++ b/src/Tools/tim_tool/src/logic/project/mod.rs @@ -0,0 +1,89 @@ +use super::tim::types::Encoding; +use serde::{Deserialize, Serialize}; +use std::path::PathBuf; + +#[derive(Serialize, Deserialize)] +pub struct ImagePosition { + pub x: u16, + pub y: u16, +} + +impl ImagePosition { + pub fn new(x: u16, y: u16) -> ImagePosition { + ImagePosition{x, y} + } +} + +impl std::default::Default for ImagePosition { + fn default() -> Self { + ImagePosition::new(0, 0) + } +} + +#[derive(Clone, Copy, Serialize, Deserialize)] +pub struct ImageSize { + pub width: u16, + pub height: u16, +} + +impl ImageSize { + pub fn new(width: u16, height: u16) -> ImageSize { + ImageSize{width, height} + } +} + +impl From for (u16, u16) { + fn from(value: ImageSize) -> Self { + (value.width, value.height) + } +} + +impl std::default::Default for ImageSize { + fn default() -> Self { + ImageSize::new(0, 0) + } +} + +#[derive(Serialize, Deserialize)] +pub struct PaletteRect { + pub pos: ImagePosition, + pub size: ImageSize, +} + +impl PaletteRect { + pub fn new(x: u16, y: u16, width: u16, height: u16) -> PaletteRect { + PaletteRect{pos: ImagePosition{x, y}, size: ImageSize{width, height}} + } +} + +impl std::default::Default for PaletteRect { + fn default() -> Self { + PaletteRect{pos: ImagePosition::default(), size: ImageSize::default()} + } +} + +#[derive(Serialize, Deserialize)] +pub struct Job { + pub name: String, + pub file_path: PathBuf, + pub image_pos: ImagePosition, + pub palette_rect: PaletteRect, + pub encoding: Encoding, +} + +impl Job { + pub fn new(name: String, file_path: PathBuf, image_pos: (u16, u16), encoding: Encoding) -> Job { + Job{name, file_path, image_pos: ImagePosition{x: image_pos.0, y: image_pos.1}, palette_rect: PaletteRect::default(), encoding} + } +} + +#[derive(Serialize, Deserialize)] +pub struct Project { + pub jobs: Vec +} + +impl Project { + pub fn new(jobs: Vec) -> Project { + Project{jobs} + } +} \ No newline at end of file diff --git a/src/Tools/tim_tool/src/logic/tim/mod.rs b/src/Tools/tim_tool/src/logic/tim/mod.rs index ab0f8c5b..bb27bd7d 100644 --- a/src/Tools/tim_tool/src/logic/tim/mod.rs +++ b/src/Tools/tim_tool/src/logic/tim/mod.rs @@ -5,8 +5,9 @@ use slint::{Rgba8Pixel, SharedPixelBuffer}; use tool_helper::Error; use types::Encoding; +#[derive(Clone)] pub struct TIMInfo { - _path: PathBuf, + path: PathBuf, image_data: SharedPixelBuffer, palette: Option, } @@ -52,7 +53,7 @@ impl TIMInfo { _ => {return Err(Error::from_str("Only 4 and 8bit color depth are supported for indexed color images"));} } } - Ok(TIMInfo{_path: path.clone(), image_data, palette: Some(PaletteInfo::new(palette_colors))}) + Ok(TIMInfo{path: path.clone(), image_data, palette: Some(PaletteInfo::new(palette_colors))}) } else { @@ -70,11 +71,11 @@ impl TIMInfo { _ => {return Err(Error::from_str("Only 8bit color depth are supported for direct color images"));} } } - Ok(TIMInfo{_path: path.clone(), image_data, palette: None}) + Ok(TIMInfo{path: path.clone(), image_data, palette: None}) } } - pub fn change_palette_size(&mut self, width: u32, height: u32) -> Result, Error> { + pub fn change_palette_size(&mut self, width: u16, height: u16) -> Result, Error> { if let Some(palette) = &mut self.palette { if width == 0 || height == 0 { return Err(Error::from_text(format!("{}px x {}px is not a valid size for palette", width, height))); @@ -117,12 +118,27 @@ impl TIMInfo { None }) } + + pub fn get_palette_size(&self) -> Option<(u16, u16)> { + if let Some(palette_info) = &self.palette { + Some((palette_info.width, palette_info.height)) + } + + else { + None + } + } + + pub fn get_path(&self) -> std::path::PathBuf { + self.path.clone() + } } +#[derive(Clone)] struct PaletteInfo { data: Vec, - width: u32, - height: u32, + width: u16, + height: u16, } impl PaletteInfo { @@ -132,7 +148,7 @@ impl PaletteInfo { } pub fn get_image(&self) -> slint::Image { - let mut image_data = SharedPixelBuffer::new(self.width, self.height); + let mut image_data = SharedPixelBuffer::new(self.width as u32, self.height as u32); let dst_pixels = image_data.make_mut_slice(); for (idx, byte) in dst_pixels.iter_mut().enumerate() { diff --git a/src/Tools/tim_tool/src/logic/tim/types.rs b/src/Tools/tim_tool/src/logic/tim/types.rs index 01ef2f10..8b84f01d 100644 --- a/src/Tools/tim_tool/src/logic/tim/types.rs +++ b/src/Tools/tim_tool/src/logic/tim/types.rs @@ -1,4 +1,6 @@ -#[derive(Clone, Copy)] +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Copy, Deserialize, Serialize)] pub enum Encoding { FourBit, EightBit, @@ -17,4 +19,13 @@ impl Encoding { Encoding::FullColor => Self::FULL_COLOR_NAME, } } + + pub fn from_str(str: &str) -> Encoding { + match str { + Self::FOUR_BIT_NAME => Encoding::FourBit, + Self::EIGHT_BIT_NAME => Encoding::EightBit, + Self::FULL_COLOR_NAME => Encoding::FullColor, + _ => Encoding::FullColor, + } + } } \ No newline at end of file diff --git a/src/Tools/tim_tool/src/logic/tim_mgr.rs b/src/Tools/tim_tool/src/logic/tim_mgr.rs index 36d09310..930df311 100644 --- a/src/Tools/tim_tool/src/logic/tim_mgr.rs +++ b/src/Tools/tim_tool/src/logic/tim_mgr.rs @@ -13,6 +13,10 @@ impl TIMManager { TIMManager{added_tims: Default::default(), unadded_tim: None} } + pub fn clear(&mut self) { + self.added_tims.clear(); + } + pub fn remove_added_tim(&mut self, range: RangeInclusive) { let idx = *range.start(); for _ in range { @@ -60,7 +64,7 @@ impl TIMManager { } } - pub fn change_unadded_tim_palette_size(&mut self, width: u32, height: u32) -> Result, Error> { + pub fn change_unadded_tim_palette_size(&mut self, width: u16, height: u16) -> Result, Error> { if let Some(unadded_tim) = &mut self.unadded_tim { unadded_tim.change_palette_size(width, height) } @@ -69,4 +73,8 @@ impl TIMManager { Ok(None) } } + + pub fn clone_added_tims(&self) -> Vec> { + self.added_tims.clone() + } } \ No newline at end of file diff --git a/src/Tools/tim_tool/ui/app-window.slint b/src/Tools/tim_tool/ui/app-window.slint index 5d16b2c0..66615c4e 100644 --- a/src/Tools/tim_tool/ui/app-window.slint +++ b/src/Tools/tim_tool/ui/app-window.slint @@ -7,13 +7,15 @@ export component MainWindow inherits Window { // Main Tab values in-out property main_tab_vram_bg <=> main_tab.vram_bg; in-out property main_tab_vram_file_list <=> main_tab.vram_files; - in-out property main_tab_vram_images <=> main_tab.vram_images; + in-out property main_tab_vram_data <=> main_tab.vram_data; callback main_tab_remove_file_clicked <=> main_tab.remove_file_clicked; callback move_vram_image <=> main_tab.move_vram_image; // Project widget values in-out property project_widget-open_project_path <=> file_tab.project_widget-open_project_path; in-out property project_widget-save_project_path <=> file_tab.project_widget-save_project_path; + in-out property project_widget-append_project_elements <=> file_tab.project_widget-append_project_elements; + in-out property project_widget-use_rel_paths <=> file_tab.project_widget-use_rel_paths; callback project_widget-load_project_clicked <=> file_tab.project_widget-load_project_clicked; callback project_widget-save_project_clicked <=> file_tab.project_widget-save_project_clicked; callback project_widget-browse_open_project_clicked <=> file_tab.project_widget-browse_open_project_clicked; @@ -80,4 +82,8 @@ export component MainWindow inherits Window { public function change_to_main() { tab_widget.current-index = 1; } + + public function clear_file_tab-current_selected_file() { + main_tab.clear_current_selection(); + } } \ No newline at end of file diff --git a/src/Tools/tim_tool/ui/tab/file-tab.slint b/src/Tools/tim_tool/ui/tab/file-tab.slint index 600f0d8f..c398e2d6 100644 --- a/src/Tools/tim_tool/ui/tab/file-tab.slint +++ b/src/Tools/tim_tool/ui/tab/file-tab.slint @@ -8,6 +8,8 @@ export enum State { component ProjectWidget inherits Rectangle { in-out property open_project_path; in-out property save_project_path; + in-out property append_project_elements; + in-out property use_rel_paths; callback load_project_clicked(); callback save_project_clicked(); @@ -24,7 +26,7 @@ component ProjectWidget inherits Rectangle { VerticalLayout { alignment: start; Text { - text: "Select a project to open. Any existing changes will be dropped"; + text: "Select a project to open."; } HorizontalLayout { alignment: start; @@ -39,6 +41,15 @@ component ProjectWidget inherits Rectangle { } } } + HorizontalLayout { + alignment: start; + CheckBox { + text: "Append elements from project to existing elements"; + toggled() => { + root.append_project_elements = self.checked; + } + } + } HorizontalLayout { alignment: start; Button { @@ -72,7 +83,8 @@ component ProjectWidget inherits Rectangle { } } CheckBox { - text: "Use relative path for files"; + text: "Use relative path for files"; + checked: root.use_rel_paths; } HorizontalLayout { alignment: start; @@ -282,6 +294,8 @@ export component FileTab inherits Rectangle { // For project widget in-out property project_widget-open_project_path; in-out property project_widget-save_project_path; + in-out property project_widget-append_project_elements; + in-out property project_widget-use_rel_paths; callback project_widget-load_project_clicked(); callback project_widget-save_project_clicked(); callback project_widget-browse_open_project_clicked(); @@ -332,9 +346,11 @@ export component FileTab inherits Rectangle { padding: 4px; alignment: start; if root.state == State.Project : ProjectWidget { - open_project_path <=> root.project_widget-open_project_path; - save_project_path <=> root.project_widget-save_project_path; - + open_project_path <=> root.project_widget-open_project_path; + save_project_path <=> root.project_widget-save_project_path; + append_project_elements <=> root.project_widget-append_project_elements; + use_rel_paths <=> root.project_widget-use_rel_paths; + load_project_clicked() => { root.project_widget-load_project_clicked(); } diff --git a/src/Tools/tim_tool/ui/tab/main-tab.slint b/src/Tools/tim_tool/ui/tab/main-tab.slint index fdd28332..6bff7047 100644 --- a/src/Tools/tim_tool/ui/tab/main-tab.slint +++ b/src/Tools/tim_tool/ui/tab/main-tab.slint @@ -1,21 +1,29 @@ import { VRAMArea } from "../vram-components.slint"; import { Button, ComboBox, GroupBox, StandardListView, LineEdit, ScrollView, Slider } from "std-widgets.slint"; -struct VRAMImage { - full_img: image, - img: image, +struct VRAMImgData { + full_image: image, + image: image, +} + +struct VRAMInfo { x: int, y: int, encoding_str: string, palette_count: int, - is_palette: bool, + is_palette: bool, // < Can we combine the palette into this, instead of having it separate? +} + +struct VRAMData { + images: VRAMImgData, + info: VRAMInfo, } export component MainTab inherits Rectangle { - property scale: 1.0; + property scale: 1.0; in-out property vram_bg; - in-out property <[StandardListViewItem]> vram_files: []; - in-out property <[VRAMImage]> vram_images: []; + in-out property <[StandardListViewItem]> vram_files: []; + in-out property <[VRAMData]> vram_data: []; callback add_file_clicked(); callback remove_file_clicked(int); @@ -54,12 +62,12 @@ export component MainTab inherits Rectangle { scale: scale; } - for vram_image[i] in root.vram_images: VRAMArea { + for vram_data[i] in root.vram_data: VRAMArea { x: root.get_border_width()*1px; y: root.get_border_width()*1px; - img: vram_image.img; - img_x: vram_image.x; - img_y: vram_image.y; + img: vram_data.images.image; + img_x: vram_data.info.x; + img_y: vram_data.info.y; scale: scale; TouchArea { @@ -82,8 +90,8 @@ export component MainTab inherits Rectangle { if event.kind == PointerEventKind.down { cur_sel_x.text = parent.img_x; cur_sel_y.text = parent.img_y; - cur_sel_img.source = vram-image.full_img; - encoding_text.encoding_str = vram-image.encoding_str; + cur_sel_img.source = vram_data.images.full_image; + encoding_text.encoding_str = vram_data.info.encoding_str; cur_sel_img.visible = true; vram_files_list.current-item = i; @@ -154,10 +162,10 @@ export component MainTab inherits Rectangle { model: root.vram_files; current-item-changed(current-item) => { - cur_sel_x.text = root.vram_images[current-item].x; - cur_sel_y.text = root.vram_images[current-item].y; - cur_sel_img.source = root.vram_images[current-item].full_img; - encoding_text.encoding_str = root.vram_images[current-item].encoding_str; + cur_sel_x.text = root.vram_data[current-item].info.x; + cur_sel_y.text = root.vram_data[current-item].info.y; + cur_sel_img.source = root.vram_data[current-item].images.full_image; + encoding_text.encoding_str = root.vram_data[current-item].info.encoding_str; cur_sel_img.visible = true; } } @@ -171,10 +179,7 @@ export component MainTab inherits Rectangle { text: "Remove file"; clicked => { root.remove_file_clicked(vram_files_list.current_item); - vram_files_list.current-item = -1; - cur_sel_x.text = 0; - cur_sel_y.text = 0; - cur_sel_img.visible = false; + root.clear_current_selection(); } } } @@ -210,8 +215,8 @@ export component MainTab inherits Rectangle { accepted(text) => { if(vram_files_list.current-item != -1) { - root.move_vram_image(vram_files_list.current-item, text.to-float() - vram_images[vram_files_list.current-item].x, 0); - self.text = vram_images[vram_files_list.current-item].x; + root.move_vram_image(vram_files_list.current-item, text.to-float() - vram_data[vram_files_list.current-item].info.x, 0); + self.text = vram_data[vram_files_list.current-item].info.x; } } } @@ -231,8 +236,8 @@ export component MainTab inherits Rectangle { accepted(text) => { if(vram_files_list.current-item != -1) { - root.move_vram_image(vram_files_list.current-item, 0, text.to-float() - vram_images[vram_files_list.current-item].y); - self.text = vram_images[vram_files_list.current-item].y; + root.move_vram_image(vram_files_list.current-item, 0, text.to-float() - vram_data[vram_files_list.current-item].info.y); + self.text = vram_data[vram_files_list.current-item].info.y; } } } @@ -252,22 +257,22 @@ export component MainTab inherits Rectangle { if(vram_files_list.current-item != -1) { if(event.text == Key.LeftArrow) { root.move_vram_image(vram_files_list.current-item, -1, 0); - cur_sel_x.text = vram_images[vram_files_list.current-item].x; + cur_sel_x.text = vram_data[vram_files_list.current-item].info.x; } if(event.text == Key.RightArrow) { root.move_vram_image(vram_files_list.current-item, 1, 0); - cur_sel_x.text = vram_images[vram_files_list.current-item].x; + cur_sel_x.text = vram_data[vram_files_list.current-item].info.x; } if(event.text == Key.UpArrow) { root.move_vram_image(vram_files_list.current-item, 0, -1); - cur_sel_y.text = vram_images[vram_files_list.current-item].y; + cur_sel_y.text = vram_data[vram_files_list.current-item].info.y; } if(event.text == Key.DownArrow) { root.move_vram_image(vram_files_list.current-item, 0, 1); - cur_sel_y.text = vram_images[vram_files_list.current-item].y; + cur_sel_y.text = vram_data[vram_files_list.current-item].info.y; } } accept @@ -277,4 +282,11 @@ export component MainTab inherits Rectangle { function get_border_width() -> int { return 4; } + + public function clear_current_selection() { + vram_files_list.current-item = -1; + cur_sel_x.text = 0; + cur_sel_y.text = 0; + cur_sel_img.visible = false; + } } \ No newline at end of file