Compare commits

...

11 Commits

Author SHA1 Message Date
Jaby 3f507fd19e Setup usage example 2025-04-08 21:22:39 +02:00
Jaby f8ec08008a Support settings and encode 2025-04-08 19:19:32 +00:00
Jaby 4942012da1 Added storage for settings 2025-04-08 19:19:32 +00:00
Jaby 9501954587 Slowly introducing file settings 2025-04-08 19:19:32 +00:00
Jaby 751533dcf6 Move primitives around 2025-04-08 19:19:32 +00:00
Jaby 5a1997873d Make palette optional 2025-04-08 19:19:32 +00:00
Jaby 5cd29460fb Prepare GUI for additonal settings 2025-04-08 19:19:32 +00:00
Jaby 7a2052575d Prepare file settings box 2025-04-08 19:19:32 +00:00
Jaby a475a0c82d Bump dependency versions 2025-04-05 22:38:14 +02:00
Jaby 32da34cad6 Fix various focus issues 2025-04-05 22:34:30 +02:00
Jaby af633a4fdf Fix scoll issue 2025-04-05 22:22:33 +02:00
9 changed files with 1510 additions and 445 deletions

View File

@ -26,7 +26,6 @@ INPUT += $(OUTPUT_DIR)/Controller.img
Controller_FLAGS = $(CLUT_4_COLOR_TRANS_FLAGS)
INPUT += $(OUTPUT_DIR)/doener_fish.img
doener_fish_FLAGS = $(CLUT_4_COLOR_TRANS_FLAGS)
INPUT += $(OUTPUT_DIR)/JabyStar.img
JabyStar_FLAGS = $(CLUT_4_COLOR_TRANS_FLAGS)
@ -44,6 +43,10 @@ $(OUTPUT_DIR)/%.vag: audio/%.wav
@mkdir -p $(OUTPUT_DIR)
psxfileconv --lz4 $< -o $@ vag
$(OUTPUT_DIR)/doener_fish.img: doener_fish.png vram_doener_fish.tim_project
@mkdir -p $(OUTPUT_DIR)
psxfileconv vram_doener_fish.tim_project -o $@ project
$(OUTPUT_DIR)/OnMyOwn_BailBonds.xa: audio/OnMyOwn_BailBonds.mp3
@mkdir -p $(OUTPUT_DIR)
psxfileconv $< -o $@ xa

View File

@ -0,0 +1 @@
{"jobs":[{"name":"doener_fish.png","file_path":"doener_fish.png","image_pos":{"x":926,"y":0},"palette_rect":{"pos":{"x":976,"y":510},"size":{"width":16,"height":1}},"encoding":"FourBit"}]}

File diff suppressed because it is too large Load Diff

View File

@ -9,15 +9,15 @@ panic = "abort"
[dependencies]
pathdiff = "0.2.3"
png = "0.17.16"
rfd = "0.15.2"
slint = "1.9.2"
serde = {version = "1.0.140", features = ["derive"]}
rfd = "0.15.3"
slint = "1.10.0"
serde = {version = "1.0.219", features = ["derive"]}
serde_json = "1.0"
tiny-skia = "0.11.4"
tool_helper = {path = "../tool_helper"}
[build-dependencies]
slint-build = "1.9.2"
slint-build = "1.10.0"
[package.metadata.bundle]
name = "TIMTOOL"

View File

@ -4,7 +4,7 @@ use crate::{gui::{self, main_tab::{MainTab, NewVRAMImageInfo}, MutexTIMManager,
use super::FileTab;
use rfd::FileDialog;
use slint::SharedString;
use tim_tool::logic::{project::{ImagePosition, Job, PaletteRect, Project}, tim::types::Encoding, TIMManager};
use tim_tool::logic::{project::{FileSettings, ImagePosition, Job, PaletteRect, Project, Transparency}, 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 {
@ -46,7 +46,7 @@ pub(super) fn on_add_image(tim_manager: MutexTIMManager) -> impl FnMut(&mut File
let file_name = file_tab.get_file_name();
let encoding = file_tab.get_encoding()?;
add_unadded_tim(main_tab, &mut tim_manager.lock().expect("VRAM already locked"), UnaddedTIM::new(&file_name, encoding))?;
add_unadded_tim(main_tab, &mut tim_manager.lock().expect("VRAM already locked"), UnaddedTIM::new(&file_name, encoding), FileSettings::from_encoding(encoding))?;
file_tab.clear_load();
main_window.invoke_change_to_main();
@ -77,20 +77,20 @@ pub(super) fn on_load_project_clicked(tim_manager: MutexTIMManager) -> impl FnMu
}
for job in new_project.jobs {
let file_path = Job::create_file_path(job.file_path, open_location.as_str());
let (_, _) = tim_manager.load_unadded_tim(&file_path)?;
let (pal_width, pal_height) = job.palette_rect.size.into();
let file_path = Job::create_file_path(job.file_path, open_location.as_str());
let (_, _) = tim_manager.load_unadded_tim(&file_path)?;
let (pal_x, pal_y, pal_width, pal_height) = if let Some(palette_rect) = job.palette_rect {palette_rect.into()} else {(0, 0, 0, 0)};
tim_manager.change_unadded_tim_palette_size(pal_width, pal_height)?;
let unadded_tim = 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
palette_x: pal_x,
palette_y: pal_y,
encoding: job.settings.encoding
};
add_unadded_tim(main_tab, &mut tim_manager, unadded_tim)?;
add_unadded_tim(main_tab, &mut tim_manager, unadded_tim, job.settings)?;
}
main_window.invoke_change_to_main();
@ -126,7 +126,7 @@ pub(super) fn on_save_project_clicked(tim_manager: MutexTIMManager) -> impl FnMu
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;
cur_job.palette_rect = Some(cur_palette);
}
}
None
@ -149,7 +149,11 @@ pub(super) fn on_save_project_clicked(tim_manager: MutexTIMManager) -> impl FnMu
}
}
} else {tim.get_path()};
cur_job = Some(Job::new(name, file_path, (vram.x as u16, vram.y as u16), Encoding::from_str(vram.encoding_str.as_str())));
cur_job = Some(Job::new(name, file_path, (vram.x as u16, vram.y as u16), FileSettings::new(
vram.use_compression,
Transparency::from(vram.trans_setting),
Encoding::from_str(vram.encoding_str.as_str())
)));
}
prev_job
}
@ -220,11 +224,11 @@ impl<'a> UnaddedTIM<'a> {
}
}
fn add_unadded_tim(main_tab: &mut MainTab, tim_manager: &mut TIMManager, unadded_tim: UnaddedTIM) -> Result<(), Error> {
fn add_unadded_tim(main_tab: &mut MainTab, tim_manager: &mut TIMManager, unadded_tim: UnaddedTIM, settings: FileSettings) -> 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 image = NewVRAMImageInfo::new(image, unadded_tim.image_x, unadded_tim.image_y, settings);
let palette_image = if let Some(palette_image) = palette_image { Some(NewVRAMImageInfo::new(palette_image, unadded_tim.palette_x, unadded_tim.palette_y, FileSettings::from_encoding(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) {

View File

@ -4,18 +4,18 @@ use crate::{gui::{MutexTIMManager, VRAM_HEIGHT, VRAM_WIDTH}, VRAMImgData, VRAMIn
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;
use tim_tool::logic::project::FileSettings;
pub struct NewVRAMImageInfo {
pub image: slint::Image,
pub x: u16,
pub y: u16,
pub encoding: Encoding,
pub image: slint::Image,
pub x: u16,
pub y: u16,
pub settings: FileSettings,
}
impl NewVRAMImageInfo {
pub fn new(image: slint::Image, x: u16, y: u16, encoding: Encoding) -> NewVRAMImageInfo {
NewVRAMImageInfo{image, x, y, encoding}
pub fn new(image: slint::Image, x: u16, y: u16, settings: FileSettings) -> NewVRAMImageInfo {
NewVRAMImageInfo{image, x, y, settings}
}
}
@ -84,16 +84,18 @@ impl MainTab {
pub fn add_new_vram_file(&mut self, file_name: &String, full_image: slint::Image, texture: NewVRAMImageInfo, image_palette: Option<NewVRAMImageInfo>) -> usize {
let mut vram_data = self.vram.lock().expect("VRAM already locked");
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 {"<Palette>"} else {vram_image.encoding.to_str()};
let encoding_str = if is_palette {"<Palette>"} else {vram_image.settings.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),
x: vram_image.x as i32,
y: vram_image.y as i32,
encoding_str: SharedString::from(encoding_str),
use_compression: vram_image.settings.compress,
trans_setting: vram_image.settings.transparency.as_idx(),
palette_count,
is_palette,
}

View File

@ -2,6 +2,72 @@ use super::tim::types::Encoding;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
#[derive(Serialize, Deserialize)]
pub struct FileSettings {
pub compress: bool,
pub transparency: Transparency,
pub encoding: Encoding,
}
impl FileSettings {
pub fn new(compress: bool, transparency: Transparency, encoding: Encoding) -> FileSettings {
FileSettings{compress, transparency, encoding}
}
pub fn from_encoding(encoding: Encoding) -> FileSettings {
let mut new_type = FileSettings::default();
new_type.encoding = encoding;
new_type
}
}
impl std::default::Default for FileSettings {
fn default() -> Self {
Self{compress: Default::default(), transparency: Transparency::None, encoding: Encoding::FullColor}
}
}
#[derive(Serialize, Deserialize)]
pub struct Job {
pub name: String,
pub file_path: PathBuf,
pub image_pos: ImagePosition,
pub palette_rect: Option<PaletteRect>,
pub settings: FileSettings,
}
impl Job {
pub fn new(name: String, file_path: PathBuf, image_pos: (u16, u16), settings: FileSettings) -> Job {
Job{name, file_path, image_pos: ImagePosition{x: image_pos.0, y: image_pos.1}, palette_rect: None, settings}
}
pub fn create_file_path<T:?Sized + std::convert::AsRef<std::ffi::OsStr>>(file_path: PathBuf, location_path: &T) -> PathBuf {
if file_path.is_file() {
file_path
}
else {
let mut new_file_path = PathBuf::from(location_path);
new_file_path.pop();
new_file_path.push(file_path);
new_file_path
}
}
}
#[derive(Serialize, Deserialize)]
pub struct Project {
pub jobs: Vec<Job>
}
impl Project {
pub fn new(jobs: Vec<Job>) -> Project {
Project{jobs}
}
}
#[derive(Serialize, Deserialize)]
pub struct ImagePosition {
pub x: u16,
@ -56,6 +122,12 @@ impl PaletteRect {
}
}
impl From<PaletteRect> for (u16, u16, u16, u16) {
fn from(value: PaletteRect) -> Self {
(value.pos.x, value.pos.y, value.size.width, value.size.height)
}
}
impl std::default::Default for PaletteRect {
fn default() -> Self {
PaletteRect{pos: ImagePosition::default(), size: ImageSize::default()}
@ -63,41 +135,30 @@ impl std::default::Default for PaletteRect {
}
#[derive(Serialize, Deserialize)]
pub struct Job {
pub name: String,
pub file_path: PathBuf,
pub image_pos: ImagePosition,
pub palette_rect: PaletteRect,
pub encoding: Encoding,
pub enum Transparency {
None,
FirstColor,
PSXSemi,
Both,
}
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}
impl Transparency {
pub fn from(selection: i32) -> Transparency {
match selection {
0 => Transparency::None,
1 => Transparency::FirstColor,
2 => Transparency::PSXSemi,
3 => Transparency::Both,
_ => Transparency::None,
}
}
pub fn create_file_path<T:?Sized + std::convert::AsRef<std::ffi::OsStr>>(file_path: PathBuf, location_path: &T) -> PathBuf {
if file_path.is_file() {
file_path
}
else {
let mut new_file_path = PathBuf::from(location_path);
new_file_path.pop();
new_file_path.push(file_path);
new_file_path
pub fn as_idx(&self) -> i32 {
match self {
Transparency::None => 0,
Transparency::FirstColor => 1,
Transparency::PSXSemi => 2,
Transparency::Both => 3,
}
}
}
#[derive(Serialize, Deserialize)]
pub struct Project {
pub jobs: Vec<Job>
}
impl Project {
pub fn new(jobs: Vec<Job>) -> Project {
Project{jobs}
}
}

View File

@ -81,6 +81,7 @@ export component MainWindow inherits Window {
public function change_to_main() {
tab_widget.current-index = 1;
main_tab.set_active();
}
public function clear_file_tab-current_selected_file() {

View File

@ -1,5 +1,5 @@
import { VRAMArea } from "../vram-components.slint";
import { Button, ComboBox, GroupBox, StandardListView, LineEdit, ScrollView, Slider } from "std-widgets.slint";
import { Button, CheckBox, ComboBox, GroupBox, StandardListView, LineEdit, ScrollView, Slider } from "std-widgets.slint";
struct VRAMImgData {
full_image: image,
@ -7,11 +7,13 @@ struct VRAMImgData {
}
struct VRAMInfo {
x: int,
y: int,
encoding_str: string,
palette_count: int,
is_palette: bool, // < Can we combine the palette into this, instead of having it separate?
x: int,
y: int,
encoding_str: string,
use_compression: bool,
trans_setting: int,
palette_count: int,
is_palette: bool, // < Can we combine the palette into this, instead of having it separate?
}
struct VRAMData {
@ -29,8 +31,9 @@ export component MainTab inherits Rectangle {
callback remove_file_clicked(int);
callback move_vram_image(int, int, int);
width: group.width + group.x*2;
height: group.height + group.y*2 + 32px;
width: group.width + group.x*2;
height: group.height + group.y*2 + 32px;
forward-focus: key_focus;
group := GroupBox {
title: "VRAM Layout";
@ -96,6 +99,14 @@ export component MainTab inherits Rectangle {
encoding_text.encoding_str = vram_data.info.encoding_str;
cur_sel_img.visible = true;
if !vram-data.info.is_palette {
file_settings_box.set_active(i);
}
else {
file_settings_box.set_inactive();
}
vram_files_list.current-item = i;
}
}
@ -121,10 +132,20 @@ export component MainTab inherits Rectangle {
}
function update_viewport() {
// If this value is positive, then the image moved so much to the right, that we do not use the full display area anymore
if self.viewport-x > 0 {
self.viewport-x = 0;
}
if abs(self.viewport-x) + self.width > self.viewport-width {
self.viewport-x += (self.width + abs(self.viewport-x)) - self.viewport-width;
}
if self.viewport-y > 0 {
self.viewport-y = 0;
}
// If this value is positive, then the image moved so much down, that we do not use the full display area anymore
if abs(self.viewport-y) + self.height > self.viewport-height {
self.viewport-y += (self.height + abs(self.viewport-y)) - self.viewport-height;
}
@ -171,6 +192,14 @@ export component MainTab inherits Rectangle {
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;
if !root.vram_data[current-item].info.is_palette {
file_settings_box.set_active(current-item);
}
else {
file_settings_box.set_inactive();
}
}
item-pointer-event(item, event, position) => {
@ -279,11 +308,59 @@ export component MainTab inherits Rectangle {
}
}
}
file_settings_box := GroupBox {
title: "File settings";
enabled: false;
VerticalLayout {
alignment: start;
lz4_check_box := CheckBox {
text: "Compress (lz4)";
enabled: file_settings_box.enabled;
toggled => {
if(vram_files_list.current-item != -1) {
root.vram_data[vram_files_list.current-item].info.use_compression = self.checked;
}
}
}
GroupBox {
title: "Transparency:";
enabled: file_settings_box.enabled;
VerticalLayout {
alignment: start;
trans_combo_box := ComboBox {
model: ["No transparency", "First color transparent", "PSX semi-transparency", "Both"];
enabled: file_settings_box.enabled;
selected(current-value) => {
if(vram_files_list.current-item != -1) {
root.vram_data[vram_files_list.current-item].info.trans_setting = self.current-index;
}
}
}
}
}
}
public function set_active(current-item: int) {
lz4_check_box.checked = root.vram_data[current-item].info.use_compression;
trans_combo_box.current-index = root.vram_data[current-item].info.trans_setting;
self.enabled = true;
}
public function set_inactive() {
lz4_check_box.checked = false;
trans_combo_box.current-index = 0;
self.enabled = false;
}
}
}
}
}
FocusScope {
key_focus := FocusScope {
key-pressed(event) => {
if(vram_files_list.current-item != -1) {
if(event.text == Key.LeftArrow) {
@ -320,4 +397,8 @@ export component MainTab inherits Rectangle {
cur_sel_y.text = 0;
cur_sel_img.visible = false;
}
public function set_active() {
key_focus.focus();
}
}