From 43640f0f25dae215050b583921506e43e253cd4b Mon Sep 17 00:00:00 2001 From: kelpsyberry <138107494+kelpsyberry@users.noreply.github.com> Date: Sat, 30 Mar 2024 17:24:49 +0100 Subject: [PATCH] Add "DS ROM info" debug view --- Cargo.lock | 56 +-- core/src/ds_slot/rom/header.rs | 7 +- core/src/ds_slot/rom/icon_title.rs | 12 +- core/src/emu.rs | 5 +- core/src/lib.rs | 3 +- frontend/desktop/src/debug_views.rs | 6 +- .../desktop/src/debug_views/ds_rom_info.rs | 393 ++++++++++++++++++ frontend/desktop/src/ui/utils.rs | 84 +++- frontend/desktop/src/utils.rs | 4 +- 9 files changed, 517 insertions(+), 53 deletions(-) create mode 100644 frontend/desktop/src/debug_views/ds_rom_info.rs diff --git a/Cargo.lock b/Cargo.lock index 30c965a..31bc16a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -172,7 +172,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "258b52a1aa741b9f09783b2d86cf0aeeb617bbf847f6933340a39644227acbdb" dependencies = [ "event-listener 5.2.0", - "event-listener-strategy 0.5.0", + "event-listener-strategy 0.5.1", "futures-core", "pin-project-lite", ] @@ -185,16 +185,16 @@ checksum = "f28243a43d821d11341ab73c80bed182dc015c514b951616cf79bd4af39af0c3" dependencies = [ "concurrent-queue", "event-listener 5.2.0", - "event-listener-strategy 0.5.0", + "event-listener-strategy 0.5.1", "futures-core", "pin-project-lite", ] [[package]] name = "async-executor" -version = "1.8.0" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17ae5ebefcc48e7452b4987947920dac9450be1110cadf34d1b8c116bdbaf97c" +checksum = "10b3e585719c2358d2660232671ca8ca4ddb4be4ce8a1842d6c2dc8685303316" dependencies = [ "async-lock 3.3.0", "async-task", @@ -267,19 +267,21 @@ dependencies = [ [[package]] name = "async-process" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "451e3cf68011bd56771c79db04a9e333095ab6349f7e47592b788e9b98720cc8" +checksum = "d999d925640d51b662b7b4e404224dd81de70f4aa4a199383c2c5e5b86885fa3" dependencies = [ "async-channel", "async-io", "async-lock 3.3.0", "async-signal", + "async-task", "blocking", "cfg-if", "event-listener 5.2.0", "futures-lite", "rustix", + "tracing", "windows-sys 0.52.0", ] @@ -291,7 +293,7 @@ checksum = "30c5ef0ede93efbf733c1a727f3b6b5a1060bbedd5600183e66f6e4be4af0ec5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.57", ] [[package]] @@ -326,7 +328,7 @@ checksum = "a507401cad91ec6a857ed5513a2073c82a9b9048762b885bb98655b306964681" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.57", ] [[package]] @@ -358,7 +360,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.55", + "syn 2.0.57", ] [[package]] @@ -1100,7 +1102,7 @@ source = "git+https://github.com/kelpsyberry/emu-utils#dc213ccfbcf4dfdfc15352c77 dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.57", ] [[package]] @@ -1127,7 +1129,7 @@ checksum = "5c785274071b1b420972453b306eeca06acf4633829db4223b58a2a8c5953bc4" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.57", ] [[package]] @@ -1186,9 +1188,9 @@ dependencies = [ [[package]] name = "event-listener-strategy" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "feedafcaa9b749175d5ac357452a9d41ea2911da598fde46ce1fe02c37751291" +checksum = "332f51cb23d20b0de8458b86580878211da09bcd4503cb579c225b3d124cabb3" dependencies = [ "event-listener 5.2.0", "pin-project-lite", @@ -1230,7 +1232,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.57", ] [[package]] @@ -1290,7 +1292,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.57", ] [[package]] @@ -1976,7 +1978,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.57", ] [[package]] @@ -2015,7 +2017,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.57", ] [[package]] @@ -2315,7 +2317,7 @@ checksum = "90d78755a79b5711d5e2140c36e10dac2f06f555b1a9995141b54f72ae129515" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.57", ] [[package]] @@ -2604,7 +2606,7 @@ checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.57", ] [[package]] @@ -2626,7 +2628,7 @@ checksum = "0b2e6b945e9d3df726b65d6ee24060aff8e3533d431f677a9695db04eff9dfdb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.57", ] [[package]] @@ -2812,9 +2814,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.55" +version = "2.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "002a1b3dbf967edfafc32655d0f377ab0bb7b994aa1d32c8cc7e9b8bf3ebb8f0" +checksum = "11a6ae1e52eb25aab8f3fb9fca13be982a373b8f1157ca14b897a825ba4a2d35" dependencies = [ "proc-macro2", "quote", @@ -2876,7 +2878,7 @@ checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.57", ] [[package]] @@ -2996,7 +2998,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.57", ] [[package]] @@ -3141,7 +3143,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.57", "wasm-bindgen-shared", ] @@ -3175,7 +3177,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.57", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3927,7 +3929,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.57", ] [[package]] diff --git a/core/src/ds_slot/rom/header.rs b/core/src/ds_slot/rom/header.rs index 3ce2215..2b31993 100644 --- a/core/src/ds_slot/rom/header.rs +++ b/core/src/ds_slot/rom/header.rs @@ -25,11 +25,8 @@ pub enum Region { impl<'a> Header<'a> { #[inline] - pub fn new(bytes: &'a [u8]) -> Option { - if bytes.len() < 0x170 { - return None; - } - Some(Header(bytes)) + pub fn new(bytes: &'a [u8; 0x170]) -> Self { + Header(bytes) } #[inline] diff --git a/core/src/ds_slot/rom/icon_title.rs b/core/src/ds_slot/rom/icon_title.rs index 7300c6f..75bad82 100644 --- a/core/src/ds_slot/rom/icon_title.rs +++ b/core/src/ds_slot/rom/icon_title.rs @@ -136,7 +136,12 @@ impl Titles { } let mut bytes = [0; 0x100]; rom_contents.read_slice(icon_title_offset + $offset, &mut bytes); - String::from_utf16le(&bytes).map_err(|_| Box::new(bytes)) + let end_index = bytes + .array_chunks::<2>() + .position(|c| *c == [0; 2]) + .unwrap_or(bytes.len()) + << 1; + String::from_utf16le(&bytes[..end_index]).map_err(|_| Box::new(bytes)) }}; } @@ -260,8 +265,11 @@ impl IconTitle { } pub fn read_icon_title_offset(rom_contents: &mut (impl Contents + ?Sized)) -> Option { + if 0x170 > rom_contents.len() { + return None; + } let mut header_bytes = Bytes::new([0; 0x170]); rom_contents.read_header(&mut header_bytes); - let header = Header::new(&*header_bytes)?; + let header = Header::new(&header_bytes); Some(header.icon_title_offset() as usize) } diff --git a/core/src/emu.rs b/core/src/emu.rs index 031627f..8ed963c 100644 --- a/core/src/emu.rs +++ b/core/src/emu.rs @@ -376,10 +376,9 @@ impl Emu { // TODO: More accurate direct boot let mut header_bytes = Bytes::new([0; 0x170]); + // NOTE: The ROM file's size is ensured beforehand, this should never panic. self.ds_slot.rom.read_header(&mut header_bytes); - let header = ds_slot::rom::header::Header::new(&*header_bytes) - // NOTE: The ROM file's size is ensured beforehand, this should never occur. - .expect("couldn't read DS slot ROM header"); + let header = ds_slot::rom::header::Header::new(&header_bytes); let chip_id = self.ds_slot.rom.chip_id(); macro_rules! write_main_mem { diff --git a/core/src/lib.rs b/core/src/lib.rs index 8dfa114..c1a1501 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -9,7 +9,8 @@ portable_simd, new_uninit, str_from_utf16_endian, - array_try_from_fn + array_try_from_fn, + array_chunks )] #![warn(clippy::pedantic)] #![allow( diff --git a/frontend/desktop/src/debug_views.rs b/frontend/desktop/src/debug_views.rs index c2a3cac..15519d6 100644 --- a/frontend/desktop/src/debug_views.rs +++ b/frontend/desktop/src/debug_views.rs @@ -14,6 +14,8 @@ mod bg_maps_2d; use bg_maps_2d::BgMaps2d; mod audio_channels; use audio_channels::AudioChannels; +mod ds_rom_info; +use ds_rom_info::DsRomInfo; use super::ui::window::Window; use ahash::AHashMap as HashMap; @@ -773,5 +775,7 @@ declare_structs!( (bg_maps_2d, BgMaps2d, InitBgMaps2d, DestroyBgMaps2d, BgMaps2dVisibility, BgMaps2dCustom), (audio_channels, AudioChannels, InitAudioChannels, DestroyAudioChannels, AudioChannelsVisibility, AudioChannelsCustom) ], - [] + [ + (ds_rom_info, DsRomInfo, FetchDsRomInfo, ReplyDsRomInfo) + ] ); diff --git a/frontend/desktop/src/debug_views/ds_rom_info.rs b/frontend/desktop/src/debug_views/ds_rom_info.rs new file mode 100644 index 0000000..12fbb32 --- /dev/null +++ b/frontend/desktop/src/debug_views/ds_rom_info.rs @@ -0,0 +1,393 @@ +use super::{BaseView, SingletonView, StaticView}; +use crate::{ + ui::{ + utils::{heading, heading_custom}, + window::Window, + }, + utils::icon_data_to_rgba8, +}; +use dust_core::{ + cpu, + ds_slot::rom::{ + header::{Header, Region, UnitCode}, + icon_title::{self, IconTitle}, + }, + emu::Emu, + utils::{zeroed_box, Bytes}, +}; +use imgui::{Image, StyleColor, TableColumnFlags, TableColumnSetup, TableFlags, TextureId}; +use std::borrow::Cow; + +pub struct Data { + chip_id: u32, + header_bytes: Box>, + icon_title: Option>, +} + +pub struct DsRomInfo { + chip_id: u32, + header_bytes: Box>, + icon_title: Option<(TextureId, Box)>, + show_advanced: bool, +} + +impl BaseView for DsRomInfo { + const MENU_NAME: &'static str = "DS ROM info"; +} + +const BORDER_WIDTH: f32 = 1.0; + +fn format_size(size: u32) -> String { + let log1024 = 31_u32.saturating_sub(size.leading_zeros()) / 10; + let unit = ["B", "KiB", "MiB", "GiB"][log1024 as usize]; + let amount = size as f64 / (1 << (log1024 * 10)) as f64; + format!("{amount:.2} {unit}") +} + +fn format_size_shift(shift: usize) -> String { + let units = ["B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"]; + if let Some(unit) = units.get(shift / 10) { + format!("{} {unit}, 2^{shift} B", 1 << (shift % 10)) + } else { + format!("2^{shift} B") + } +} + +impl StaticView for DsRomInfo { + type Data = Data; + + fn fetch_data(emu: &mut Emu) -> Self::Data { + let mut header_bytes = zeroed_box(); + emu.ds_slot.rom.read_header(&mut header_bytes); + let icon_title = emu.ds_slot.rom.contents().and_then(|rom_contents| { + Some(Box::new( + IconTitle::decode_at_offset( + Header::new(&header_bytes).icon_title_offset() as usize, + rom_contents, + ) + .ok()?, + )) + }); + Data { + chip_id: emu.ds_slot.rom.chip_id(), + header_bytes, + icon_title, + } + } + + fn new(data: Self::Data, window: &mut Window) -> Self { + let icon_title = data.icon_title.map(|icon_title| { + let icon_tex = window.imgui_gfx.create_owned_texture( + Some("Icon".into()), + imgui_wgpu::TextureDescriptor { + width: 32, + height: 32, + format: wgpu::TextureFormat::Rgba8Unorm, + ..Default::default() + }, + imgui_wgpu::SamplerDescriptor { + mag_filter: wgpu::FilterMode::Nearest, + min_filter: wgpu::FilterMode::Linear, + ..Default::default() + }, + ); + icon_tex.set_data( + window.gfx_device(), + window.gfx_queue(), + &*icon_data_to_rgba8( + &icon_title.default_icon.palette, + &icon_title.default_icon.pixels, + ), + Default::default(), + ); + ( + window + .imgui_gfx + .add_texture(imgui_wgpu::Texture::Owned(icon_tex)), + icon_title, + ) + }); + DsRomInfo { + chip_id: data.chip_id, + header_bytes: data.header_bytes, + icon_title, + show_advanced: false, + } + } + + fn draw(&mut self, ui: &imgui::Ui, _window: &mut Window) { + macro_rules! data { + ($name: expr, $value: expr) => { + ui.table_next_row(); + ui.table_next_column(); + ui.text(concat!($name, ":")); + ui.table_next_column(); + ui.text_wrapped($value); + }; + } + + let cell_padding = style!(ui, cell_padding); + + let header = Header::new(&self.header_bytes); + + let _table = self.icon_title.is_some().then(|| { + ui.begin_table_with_flags("root", 2, TableFlags::NO_CLIP | TableFlags::BORDERS_INNER_V) + }); + + if let Some((icon_tex_id, icon_title)) = &self.icon_title { + ui.table_setup_column_with(TableColumnSetup { + flags: TableColumnFlags::WIDTH_FIXED, + init_width_or_weight: 256.0, + ..TableColumnSetup::new("Icon/title") + }); + ui.table_setup_column("Other"); + ui.table_next_row(); + ui.table_next_column(); + + let mut cursor_pos = ui.cursor_pos(); + cursor_pos[0] += (ui.content_region_avail()[0] - 128.0) * 0.5; + ui.set_cursor_pos(cursor_pos); + Image::new(*icon_tex_id, [128.0; 2]) + .border_col(ui.style_color(StyleColor::Border)) + .build(ui); + + heading(ui, "Titles", 16.0, 5.0, BORDER_WIDTH); + + if let Some(_table) = ui.begin_table_with_sizing( + "icon-title", + 2, + TableFlags::NO_CLIP + | TableFlags::SCROLL_Y + | TableFlags::SCROLL_X + | TableFlags::SIZING_STRETCH_SAME, + [0.0, ui.content_region_avail()[1] - cell_padding[1]], + 0.0, + ) { + macro_rules! title { + ($name: expr, $value: expr) => { + data!($name, $value.as_deref().unwrap_or("")); + }; + } + + ui.table_setup_column_with(TableColumnSetup { + flags: TableColumnFlags::WIDTH_FIXED, + ..TableColumnSetup::new("Name") + }); + ui.table_setup_column("Value"); + + title!("Japanese", icon_title.titles.japanese); + title!("English", icon_title.titles.english); + title!("French", icon_title.titles.french); + title!("German", icon_title.titles.german); + title!("Italian", icon_title.titles.italian); + title!("Spanish", icon_title.titles.spanish); + if let Some(chinese) = &icon_title.titles.chinese { + title!("Chinese", chinese); + } + if let Some(korean) = &icon_title.titles.korean { + title!("Korean", korean); + } + } + + ui.table_next_column(); + } + + ui.child_window("other") + .size([0.0, ui.content_region_avail()[1] - cell_padding[1]]) + .build(|| { + if let Some(_table) = ui.begin_table_with_flags("other", 2, TableFlags::NO_CLIP) { + ui.table_setup_column_with(TableColumnSetup { + flags: TableColumnFlags::WIDTH_FIXED, + ..TableColumnSetup::new("Name") + }); + ui.table_setup_column("Value"); + + data!( + "Game title", + header + .game_title() + .map(|s| Cow::from(format!("{s:?}"))) + .unwrap_or("".into()) + ); + data!("Game code", { + let (code, str) = header.game_code(); + if code == 0 { + Cow::from("Homebrew (0)") + } else if let Some(str) = str { + format!("{str:?} ({code:#010X})").into() + } else { + format!("{code:#010X}").into() + } + }); + data!("Maker code", { + let (code, str) = header.maker_code(); + if code == 0 { + Cow::from("Homebrew (0)") + } else if let Some(str) = str { + format!("{str:?} ({code:#06X})").into() + } else { + format!("{code:#06X}").into() + } + }); + data!( + "Unit code", + match header.unit_code() { + Ok(UnitCode::Ds) => Cow::from("DS"), + Ok(UnitCode::DsAndDsi) => "DS and DSi".into(), + Ok(UnitCode::Dsi) => "DSi".into(), + Err(code) => format!("Unknown ({code:#04X})").into(), + } + ); + data!( + "ROM size", + format!( + "{} (capacity {})", + format_size(header.used_rom_size()), + format_size_shift(header.capacity().0 as usize + 17) + ) + ); + data!( + "Region", + match header.region() { + Ok(Region::Normal) => Cow::from("Normal"), + Ok(Region::Korea) => "Korea".into(), + Ok(Region::China) => "China".into(), + Err(code) => format!("Unknown ({code:#04X})").into(), + } + ); + data!("Version", format!("{:#04X}", header.version())); + data!("Auto-start", if header.auto_start() { "On" } else { "Off" }); + } + + heading_custom(ui, 16.0, 5.0, BORDER_WIDTH, ui.frame_height(), || { + ui.checkbox("Show advanced", &mut self.show_advanced); + }); + + if let Some(_table) = + ui.begin_table_with_flags("other-advanced", 2, TableFlags::NO_CLIP) + { + ui.table_setup_column_with(TableColumnSetup { + flags: TableColumnFlags::WIDTH_FIXED, + ..TableColumnSetup::new("Name") + }); + ui.table_setup_column("Value"); + + if self.show_advanced { + data!("Chip ID", format!("{:#010X}", self.chip_id)); + data!( + "Encryption seed", + match header.encryption_seed() { + Ok(seed) => format!("{}", seed.get()), + Err(seed) => format!("Unknown ({seed:#04X})"), + } + ); + data!("Icon/title", { + if let Some(icon_title) = &self.icon_title { + let size = match icon_title.1.version_crc_data.version { + icon_title::Version::Base | icon_title::Version::Chinese => { + 0xA00 + } + icon_title::Version::Korean => 0xC00, + icon_title::Version::AnimatedIcon => 0x2400, + }; + format!( + "ROM: {:#010X}..{:#010X} ({})", + header.icon_title_offset(), + header.icon_title_offset() as u64 + size as u64, + format_size(size) + ) + } else { + format!("ROM offset: {:#010X}", header.icon_title_offset()) + } + }); + data!("ARM7 payload", { + let size = header.arm7_size(); + format!( + "ROM: {:#010X}..{:#010X}\nRAM: {:#010X}..{:#010X}\n({})", + header.arm7_rom_offset(), + header.arm7_rom_offset() as u64 + size as u64, + header.arm7_ram_addr(), + header.arm7_ram_addr() as u64 + size as u64, + format_size(size) + ) + }); + data!( + "ARM7 entry address", + format!("{:#010X}", header.arm7_entry_addr()) + ); + data!("ARM9 payload", { + let size = header.arm9_size(); + format!( + "ROM: {:#010X}..{:#010X}\nRAM: {:#010X}..{:#010X}\n({})", + header.arm9_rom_offset(), + header.arm9_rom_offset() as u64 + size as u64, + header.arm9_ram_addr(), + header.arm9_ram_addr() as u64 + size as u64, + format_size(size) + ) + }); + data!( + "ARM9 entry address", + format!("{:#010X}", header.arm9_entry_addr()) + ); + data!("FNT", { + let size = header.fnt_size(); + format!( + "ROM: {:#010X}..{:#010X} ({})", + header.fnt_offset(), + header.fnt_offset() as u64 + size as u64, + format_size(size) + ) + }); + data!("FAT", { + let size = header.fat_size(); + format!( + "ROM: {:#010X}..{:#010X} ({})", + header.fat_offset(), + header.fat_offset() as u64 + size as u64, + format_size(size) + ) + }); + data!("ARM7 overlay", { + let size = header.arm7_overlay_size(); + if size == 0 { + Cow::from("Not present") + } else { + format!( + "ROM: {:#010X}..{:#010X} ({})", + header.arm7_overlay_offset(), + header.arm7_overlay_offset() as u64 + size as u64, + format_size(size) + ) + .into() + } + }); + data!("ARM9 overlay", { + let size = header.arm9_overlay_size(); + if size == 0 { + Cow::from("Not present") + } else { + format!( + "ROM: {:#010X}..{:#010X} ({})", + header.arm9_overlay_offset(), + header.arm9_overlay_offset() as u64 + size as u64, + format_size(size) + ) + .into() + } + }); + data!( + "ROMCTRL normal setting", + format!("{:#010X}", header.rom_control_normal().0) + ); + data!( + "ROMCTRL key1 setting", + format!("{:#010X}", header.rom_control_key1().0) + ); + } + } + }); + } +} + +impl SingletonView for DsRomInfo {} diff --git a/frontend/desktop/src/ui/utils.rs b/frontend/desktop/src/ui/utils.rs index 56eaf5e..ae00794 100644 --- a/frontend/desktop/src/ui/utils.rs +++ b/frontend/desktop/src/ui/utils.rs @@ -117,21 +117,22 @@ pub fn scale_to_fit_rotated( } #[allow(clippy::too_many_arguments)] -pub fn heading_options( +pub fn heading_options_custom( ui: &Ui, - text: &str, - text_indent: f32, + inner_indent: f32, line_inner_margin: f32, line_outer_margin_start: f32, line_outer_margin_end: f32, line_thickness: f32, width: f32, height: f32, + inner_height: f32, remove_item_spacing: bool, + draw: impl FnOnce(), ) { let half_line_thickness = line_thickness * 0.5; - let height = height.max(ui.text_line_height()); + let height = height.max(inner_height); let mut cursor_pos = ui.cursor_screen_pos(); if remove_item_spacing { @@ -152,20 +153,21 @@ pub fn heading_options( let separator_color = ui.style_color(StyleColor::Separator); let mid_y = cursor_pos[1] + height * 0.5; - let text_start_x = cursor_pos[0] + text_indent; - let text_start_y = mid_y - ui.text_line_height() * 0.5; - let text_end_x = text_start_x + ui.calc_text_size(text)[0]; + let inner_start_x = cursor_pos[0] + inner_indent; + let inner_start_y = mid_y - inner_height * 0.5; let line_y = mid_y - half_line_thickness; - ui.set_cursor_screen_pos([text_start_x, text_start_y]); - ui.text(text); + ui.set_cursor_screen_pos([inner_start_x, inner_start_y]); + draw(); + ui.same_line_with_spacing(0.0, 0.0); + let inner_end_x = ui.cursor_screen_pos()[0]; let draw_list = ui.get_window_draw_list(); draw_list .add_line( [line_outer_bounds[0], line_y], [ - text_start_x - line_inner_margin - half_line_thickness, + inner_start_x - line_inner_margin - half_line_thickness, line_y, ], separator_color, @@ -175,7 +177,10 @@ pub fn heading_options( draw_list .add_line( [line_outer_bounds[1], line_y], - [text_end_x + line_inner_margin - half_line_thickness, line_y], + [ + inner_end_x + line_inner_margin - half_line_thickness, + line_y, + ], separator_color, ) .thickness(line_thickness) @@ -183,6 +188,34 @@ pub fn heading_options( ui.set_cursor_screen_pos(end_pos); } +#[allow(clippy::too_many_arguments)] +pub fn heading_options( + ui: &Ui, + text: &str, + text_indent: f32, + line_inner_margin: f32, + line_outer_margin_start: f32, + line_outer_margin_end: f32, + line_thickness: f32, + width: f32, + height: f32, + remove_item_spacing: bool, +) { + heading_options_custom( + ui, + text_indent, + line_inner_margin, + line_outer_margin_start, + line_outer_margin_end, + line_thickness, + width, + height, + ui.text_line_height(), + remove_item_spacing, + || ui.text(text), + ); +} + pub fn table_row_heading( ui: &Ui, text: &str, @@ -243,13 +276,40 @@ pub fn heading_spacing( } pub fn heading(ui: &Ui, text: &str, text_indent: f32, line_inner_margin: f32, line_thickness: f32) { - heading_spacing( + heading_options( ui, text, text_indent, line_inner_margin, + 0.0, + 0.0, line_thickness, + ui.content_region_avail()[0], 0.0, + false, + ); +} + +pub fn heading_custom( + ui: &Ui, + inner_indent: f32, + line_inner_margin: f32, + line_thickness: f32, + inner_height: f32, + draw: impl FnOnce(), +) { + heading_options_custom( + ui, + inner_indent, + line_inner_margin, + 0.0, + 0.0, + line_thickness, + ui.content_region_avail()[0], + 0.0, + inner_height, + false, + draw, ); } diff --git a/frontend/desktop/src/utils.rs b/frontend/desktop/src/utils.rs index f297402..0c4af30 100644 --- a/frontend/desktop/src/utils.rs +++ b/frontend/desktop/src/utils.rs @@ -236,7 +236,7 @@ pub fn icon_data_to_rgba8( palette: &icon_title::Palette, pixels: &icon_title::Pixels, ) -> Box<[u8; 0x1000]> { - let palette: [u32; 8] = array::from_fn(|i| { + let palette: [u32; 0x10] = array::from_fn(|i| { if i == 0 { return 0; } @@ -245,7 +245,7 @@ pub fn icon_data_to_rgba8( 0xFF00_0000 | rgb6 << 2 | (rgb6 >> 4 & 0x03_0303) }); - let mut rgba = zeroed_box::<[u8; 32 * 32 * 4]>(); + let mut rgba = zeroed_box::<[u8; 0x1000]>(); for (i, pixel) in pixels.iter().enumerate() { for (j, component) in palette[*pixel as usize] .to_le_bytes()