Compare commits
8
Commits
4004047d68
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6e08830b43 | ||
|
|
8083bc8069 | ||
|
|
63d923ad10 | ||
|
|
bdec3baa74 | ||
|
|
55d3cf737d | ||
|
|
36cfd961da | ||
|
|
9bcd99e437 | ||
|
|
f17d62876b |
Generated
+1
-1
@@ -2040,7 +2040,7 @@ checksum = "20870f649af7073d53e38067b2a84312175d56ea15217e1b15bc83506ec50afb"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lemur"
|
name = "lemur"
|
||||||
version = "0.15.1"
|
version = "0.15.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"atoi",
|
"atoi",
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ description = "An emulator for the Virtual Boy."
|
|||||||
repository = "https://git.virtual-boy.com/PVB/lemur"
|
repository = "https://git.virtual-boy.com/PVB/lemur"
|
||||||
publish = false
|
publish = false
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
version = "0.15.1"
|
version = "0.15.2"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|||||||
+8
-4
@@ -23,7 +23,7 @@ use winit::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
config::{AppConfig, CliArgs},
|
config::{AppConfig, CliArgs, Config},
|
||||||
controller::ControllerManager,
|
controller::ControllerManager,
|
||||||
emulator::{EmulatorClient, EmulatorCommand, SimId},
|
emulator::{EmulatorClient, EmulatorCommand, SimId},
|
||||||
filesystem::Persistence,
|
filesystem::Persistence,
|
||||||
@@ -94,7 +94,7 @@ impl Application {
|
|||||||
handle: OwnedDisplayHandle,
|
handle: OwnedDisplayHandle,
|
||||||
persistence: Persistence,
|
persistence: Persistence,
|
||||||
args: CliArgs,
|
args: CliArgs,
|
||||||
config: AppConfig,
|
config: Config<AppConfig>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let wgpu = WgpuState::new(handle, config);
|
let wgpu = WgpuState::new(handle, config);
|
||||||
let icon = load_icon().ok().map(Arc::new);
|
let icon = load_icon().ok().map(Arc::new);
|
||||||
@@ -179,6 +179,9 @@ impl Application {
|
|||||||
if args.registers {
|
if args.registers {
|
||||||
initial_windows.push(ChildWindow::Registers);
|
initial_windows.push(ChildWindow::Registers);
|
||||||
}
|
}
|
||||||
|
if args.memory {
|
||||||
|
initial_windows.push(ChildWindow::Memory);
|
||||||
|
}
|
||||||
if args.terminal {
|
if args.terminal {
|
||||||
initial_windows.push(ChildWindow::Terminal);
|
initial_windows.push(ChildWindow::Terminal);
|
||||||
}
|
}
|
||||||
@@ -504,11 +507,11 @@ impl ApplicationHandler<UserEvent> for Application {
|
|||||||
struct WgpuState {
|
struct WgpuState {
|
||||||
instance: wgpu::Instance,
|
instance: wgpu::Instance,
|
||||||
device_descriptor: Arc<dyn Fn(&wgpu::Adapter) -> wgpu::DeviceDescriptor<'static> + Send + Sync>,
|
device_descriptor: Arc<dyn Fn(&wgpu::Adapter) -> wgpu::DeviceDescriptor<'static> + Send + Sync>,
|
||||||
config: AppConfig,
|
config: Config<AppConfig>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WgpuState {
|
impl WgpuState {
|
||||||
fn new(handle: OwnedDisplayHandle, config: AppConfig) -> Self {
|
fn new(handle: OwnedDisplayHandle, config: Config<AppConfig>) -> Self {
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
let egui_wgpu::WgpuSetupCreateNew {
|
let egui_wgpu::WgpuSetupCreateNew {
|
||||||
instance_descriptor: wgpu::InstanceDescriptor { backends, .. },
|
instance_descriptor: wgpu::InstanceDescriptor { backends, .. },
|
||||||
@@ -531,6 +534,7 @@ impl WgpuState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn init(&mut self, window: &Window) -> egui_wgpu::WgpuSetupExisting {
|
fn init(&mut self, window: &Window) -> egui_wgpu::WgpuSetupExisting {
|
||||||
|
self.config.refresh();
|
||||||
let power_preference =
|
let power_preference =
|
||||||
wgpu::PowerPreference::from_env().unwrap_or(self.config.power_preference);
|
wgpu::PowerPreference::from_env().unwrap_or(self.config.power_preference);
|
||||||
debug!("power preference: {power_preference:?}");
|
debug!("power preference: {power_preference:?}");
|
||||||
|
|||||||
+17
-23
@@ -1,9 +1,9 @@
|
|||||||
use anyhow::Result;
|
|
||||||
use bytemuck::NoUninit;
|
use bytemuck::NoUninit;
|
||||||
use clap::{Parser, ValueEnum};
|
use clap::{Parser, ValueEnum};
|
||||||
use egui::{Color32, Pos2, Vec2};
|
use egui::{Color32, Pos2, Vec2};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
pub use crate::filesystem::Config;
|
||||||
use crate::{emulator::SimId, filesystem::Persistence, window::DisplayMode};
|
use crate::{emulator::SimId, filesystem::Persistence, window::DisplayMode};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
@@ -36,6 +36,9 @@ pub struct CliArgs {
|
|||||||
/// Open registers window
|
/// Open registers window
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
pub registers: bool,
|
pub registers: bool,
|
||||||
|
/// Open memory window
|
||||||
|
#[arg(short, long)]
|
||||||
|
pub memory: bool,
|
||||||
/// Open terminal
|
/// Open terminal
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
pub terminal: bool,
|
pub terminal: bool,
|
||||||
@@ -116,38 +119,31 @@ pub struct AppConfig {
|
|||||||
#[serde(default = "default_save_data_location")]
|
#[serde(default = "default_save_data_location")]
|
||||||
pub save_data_location: SaveDataLocation,
|
pub save_data_location: SaveDataLocation,
|
||||||
}
|
}
|
||||||
|
impl Default for AppConfig {
|
||||||
impl AppConfig {
|
fn default() -> Self {
|
||||||
pub fn load(persistence: &Persistence) -> Self {
|
|
||||||
if let Ok(config) = persistence.load_config(APP_CONFIG_FILENAME) {
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
Self {
|
Self {
|
||||||
power_preference: default_power_preference(),
|
power_preference: default_power_preference(),
|
||||||
force_fallback: false,
|
force_fallback: false,
|
||||||
save_data_location: default_save_data_location(),
|
save_data_location: default_save_data_location(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn save(&self, persistence: &Persistence) -> Result<()> {
|
impl AppConfig {
|
||||||
persistence.save_config(APP_CONFIG_FILENAME, &self)
|
pub fn load(persistence: &Persistence) -> Config<Self> {
|
||||||
|
persistence.config(APP_CONFIG_FILENAME)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(&mut self, args: &CliArgs) -> bool {
|
pub fn apply_updates(&mut self, args: &CliArgs) {
|
||||||
let mut updated = false;
|
|
||||||
if let Some(pref) = args.wgpu_power_preference {
|
if let Some(pref) = args.wgpu_power_preference {
|
||||||
self.power_preference = pref.into();
|
self.power_preference = pref.into();
|
||||||
updated = true;
|
|
||||||
}
|
}
|
||||||
if let Some(force_fallback) = args.wgpu_force_fallback {
|
if let Some(force_fallback) = args.wgpu_force_fallback {
|
||||||
self.force_fallback = force_fallback;
|
self.force_fallback = force_fallback;
|
||||||
updated = true;
|
|
||||||
}
|
}
|
||||||
if let Some(save_data_location) = args.save_data_location {
|
if let Some(save_data_location) = args.save_data_location {
|
||||||
self.save_data_location = save_data_location;
|
self.save_data_location = save_data_location;
|
||||||
updated = true;
|
|
||||||
}
|
}
|
||||||
updated
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,12 +167,8 @@ pub struct SimConfig {
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub position: Option<Pos2>,
|
pub position: Option<Pos2>,
|
||||||
}
|
}
|
||||||
|
impl Default for SimConfig {
|
||||||
impl SimConfig {
|
fn default() -> Self {
|
||||||
pub fn load(persistence: &Persistence, sim_id: SimId) -> Self {
|
|
||||||
if let Ok(config) = persistence.load_config(sim_config_filename(sim_id)) {
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
Self {
|
Self {
|
||||||
display_mode: DisplayMode::Anaglyph,
|
display_mode: DisplayMode::Anaglyph,
|
||||||
colors: COLOR_PRESETS[0],
|
colors: COLOR_PRESETS[0],
|
||||||
@@ -187,9 +179,11 @@ impl SimConfig {
|
|||||||
position: None,
|
position: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn save(&self, persistence: &Persistence, sim_id: SimId) -> Result<()> {
|
impl SimConfig {
|
||||||
persistence.save_config(sim_config_filename(sim_id), self)
|
pub fn load(persistence: &Persistence, sim_id: SimId) -> Config<Self> {
|
||||||
|
persistence.config(sim_config_filename(sim_id))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
use std::{ffi::c_void, sync::{LazyLock, Mutex}};
|
use std::{
|
||||||
|
ffi::c_void,
|
||||||
|
sync::{LazyLock, Mutex},
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct Reallocator {
|
struct Reallocator {
|
||||||
@@ -55,7 +58,8 @@ impl Reallocator {
|
|||||||
// actually reallocate and copy the data over
|
// actually reallocate and copy the data over
|
||||||
let mut new_data = Allocation::new(new_size);
|
let mut new_data = Allocation::new(new_size);
|
||||||
let bytes_to_copy = old_data.size.min(new_size);
|
let bytes_to_copy = old_data.size.min(new_size);
|
||||||
new_data.as_mut_slice()[..bytes_to_copy].copy_from_slice(&old_data.as_slice()[..bytes_to_copy]);
|
new_data.as_mut_slice()[..bytes_to_copy]
|
||||||
|
.copy_from_slice(&old_data.as_slice()[..bytes_to_copy]);
|
||||||
self.store_allocation(new_data)
|
self.store_allocation(new_data)
|
||||||
}
|
}
|
||||||
} else if new_size > 0 {
|
} else if new_size > 0 {
|
||||||
@@ -71,7 +75,11 @@ impl Reallocator {
|
|||||||
if ptr.is_null() {
|
if ptr.is_null() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let index = self.allocations.iter().enumerate().find_map(|(i, alloc)| std::ptr::addr_eq(alloc.ptr, ptr).then_some(i))?;
|
let index = self
|
||||||
|
.allocations
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.find_map(|(i, alloc)| std::ptr::addr_eq(alloc.ptr, ptr).then_some(i))?;
|
||||||
let allocation = self.allocations.remove(index);
|
let allocation = self.allocations.remove(index);
|
||||||
Some(allocation)
|
Some(allocation)
|
||||||
}
|
}
|
||||||
@@ -83,7 +91,8 @@ impl Reallocator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static VBU_ALLOC: LazyLock<Mutex<Reallocator>> = LazyLock::new(|| Mutex::new(Reallocator::default()));
|
static VBU_ALLOC: LazyLock<Mutex<Reallocator>> =
|
||||||
|
LazyLock::new(|| Mutex::new(Reallocator::default()));
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
#[allow(nonstandard_style)]
|
#[allow(nonstandard_style)]
|
||||||
|
|||||||
+116
-1
@@ -1,4 +1,14 @@
|
|||||||
use std::{fs, path::PathBuf};
|
use std::{
|
||||||
|
any::Any,
|
||||||
|
collections::{HashMap, hash_map::Entry},
|
||||||
|
fs,
|
||||||
|
ops::Deref,
|
||||||
|
path::PathBuf,
|
||||||
|
sync::{
|
||||||
|
Arc, Mutex, RwLock,
|
||||||
|
atomic::{AtomicU64, Ordering},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
use anyhow::{Result, bail};
|
use anyhow::{Result, bail};
|
||||||
use directories::ProjectDirs;
|
use directories::ProjectDirs;
|
||||||
@@ -7,12 +17,56 @@ use serde::{Deserialize, Serialize};
|
|||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Persistence {
|
pub struct Persistence {
|
||||||
config_dir: Option<PathBuf>,
|
config_dir: Option<PathBuf>,
|
||||||
|
states: Arc<Mutex<HashMap<String, Arc<dyn Any + Send + Sync>>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Persistence {
|
impl Persistence {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
config_dir: init_config_dir(),
|
config_dir: init_config_dir(),
|
||||||
|
states: Arc::new(Mutex::new(HashMap::new())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn config<T: ConfigCompatible>(&self, file: &str) -> Config<T> {
|
||||||
|
let state = {
|
||||||
|
let mut lock = self.states.lock().unwrap();
|
||||||
|
match lock.entry(file.to_string()) {
|
||||||
|
Entry::Vacant(e) => {
|
||||||
|
let path = self
|
||||||
|
.config_dir
|
||||||
|
.as_ref()
|
||||||
|
.map(|p| p.join(file).with_extension("json"));
|
||||||
|
let value = if let Some(p) = path.as_ref()
|
||||||
|
&& let Ok(bytes) = fs::read(p)
|
||||||
|
&& let Ok(v) = serde_json::from_slice(&bytes)
|
||||||
|
{
|
||||||
|
v
|
||||||
|
} else {
|
||||||
|
T::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let state = Arc::new(ConfigState {
|
||||||
|
current: RwLock::new(value),
|
||||||
|
version: AtomicU64::new(0),
|
||||||
|
path,
|
||||||
|
});
|
||||||
|
e.insert(state.clone());
|
||||||
|
state
|
||||||
|
}
|
||||||
|
Entry::Occupied(e) => e
|
||||||
|
.get()
|
||||||
|
.clone()
|
||||||
|
.downcast()
|
||||||
|
.expect("same config file accessed with two types"),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let value = state.current.read().unwrap().clone();
|
||||||
|
let version = state.version.load(Ordering::Relaxed);
|
||||||
|
Config {
|
||||||
|
value,
|
||||||
|
version,
|
||||||
|
state,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,3 +104,64 @@ pub fn init_data_dir() -> Option<PathBuf> {
|
|||||||
fn project_dirs() -> Option<ProjectDirs> {
|
fn project_dirs() -> Option<ProjectDirs> {
|
||||||
ProjectDirs::from("com", "virtual-boy", "Lemur")
|
ProjectDirs::from("com", "virtual-boy", "Lemur")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait ConfigCompatible:
|
||||||
|
Send + Sync + 'static + Clone + Default + Eq + Serialize + for<'a> Deserialize<'a>
|
||||||
|
{
|
||||||
|
}
|
||||||
|
impl<T: Send + Sync + 'static + Clone + Default + Eq + Serialize + for<'a> Deserialize<'a>>
|
||||||
|
ConfigCompatible for T
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ConfigState<T> {
|
||||||
|
current: RwLock<T>,
|
||||||
|
version: AtomicU64,
|
||||||
|
path: Option<PathBuf>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// A wrapper for a config struct, loaded from disk and referenced across the codebase.
|
||||||
|
// Cheap to read, and lets you sync changes to all copies.
|
||||||
|
pub struct Config<T> {
|
||||||
|
value: T,
|
||||||
|
version: u64,
|
||||||
|
state: Arc<ConfigState<T>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Deref for Config<T> {
|
||||||
|
type Target = T;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ConfigCompatible> Config<T> {
|
||||||
|
pub fn refresh(&mut self) {
|
||||||
|
let version = self.state.version.load(Ordering::Relaxed);
|
||||||
|
if self.version == version {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.value = self.state.current.read().unwrap().clone();
|
||||||
|
self.version = version;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(&mut self, mut update: impl FnMut(&mut T)) {
|
||||||
|
let mut new_value = self.value.clone();
|
||||||
|
update(&mut new_value);
|
||||||
|
if self.value == new_value {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut current = self.state.current.write().unwrap();
|
||||||
|
update(&mut current);
|
||||||
|
if let Some(path) = &self.state.path
|
||||||
|
&& let Ok(bytes) = serde_json::to_vec(&*current)
|
||||||
|
{
|
||||||
|
let _ = fs::write(path, bytes);
|
||||||
|
}
|
||||||
|
let version = self.state.version.fetch_add(1, Ordering::Relaxed) + 1;
|
||||||
|
self.value = current.clone();
|
||||||
|
self.version = version;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+1
-5
@@ -220,11 +220,7 @@ impl ImageBuffer {
|
|||||||
pub fn add(&mut self, coords: (usize, usize), pixel: Color32) {
|
pub fn add(&mut self, coords: (usize, usize), pixel: Color32) {
|
||||||
let index = coords.1 * self.size[0] + coords.0;
|
let index = coords.1 * self.size[0] + coords.0;
|
||||||
let old = self.pixels[index];
|
let old = self.pixels[index];
|
||||||
self.pixels[index] = Color32::from_rgb(
|
self.pixels[index] = old + pixel;
|
||||||
old.r() + pixel.r(),
|
|
||||||
old.g() + pixel.g(),
|
|
||||||
old.b() + pixel.b(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn changed(&self, image: &ColorImage) -> bool {
|
pub fn changed(&self, image: &ColorImage) -> bool {
|
||||||
|
|||||||
+2
-3
@@ -94,9 +94,8 @@ fn main() -> Result<()> {
|
|||||||
let persistence = Persistence::new();
|
let persistence = Persistence::new();
|
||||||
|
|
||||||
let mut config = AppConfig::load(&persistence);
|
let mut config = AppConfig::load(&persistence);
|
||||||
if config.update(&args) {
|
config.update(|c| c.apply_updates(&args));
|
||||||
let _ = config.save(&persistence);
|
|
||||||
}
|
|
||||||
let p1 = SimConfig::load(&persistence, SimId::Player1);
|
let p1 = SimConfig::load(&persistence, SimId::Player1);
|
||||||
let p2 = SimConfig::load(&persistence, SimId::Player2);
|
let p2 = SimConfig::load(&persistence, SimId::Player2);
|
||||||
|
|
||||||
|
|||||||
+66
-5
@@ -37,7 +37,53 @@ impl MemoryClient {
|
|||||||
.send_command(EmulatorCommand::WatchMemory(range, Arc::downgrade(®ion)));
|
.send_command(EmulatorCommand::WatchMemory(range, Arc::downgrade(®ion)));
|
||||||
region
|
region
|
||||||
});
|
});
|
||||||
MemoryView { region }
|
MemoryView {
|
||||||
|
region,
|
||||||
|
sim,
|
||||||
|
start,
|
||||||
|
length,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update an existing view to point to a new region of memory.
|
||||||
|
// If the old and new regions overlap, it'll retain any old values.
|
||||||
|
pub fn retarget(&self, view: &mut MemoryView, sim: SimId, start: u32, length: usize) {
|
||||||
|
let range = MemoryRange { sim, start, length };
|
||||||
|
let mut regions = self.regions.lock().unwrap_or_else(|e| e.into_inner());
|
||||||
|
let region = regions
|
||||||
|
.get(&range)
|
||||||
|
.and_then(|r| r.upgrade())
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
let region = Arc::new(MemoryRegion::new(start, length));
|
||||||
|
if view.range().overlaps(range) {
|
||||||
|
let (old_index, new_index) = if start < view.start {
|
||||||
|
(0, view.start as usize - start as usize)
|
||||||
|
} else {
|
||||||
|
(start as usize - view.start as usize, 0)
|
||||||
|
};
|
||||||
|
let len = (view.length - old_index).min(length - new_index);
|
||||||
|
let mut new_data = vec![0u8; length];
|
||||||
|
let old_data = view.borrow();
|
||||||
|
for (dst, src) in new_data[new_index..(new_index + len)]
|
||||||
|
.iter_mut()
|
||||||
|
.zip(old_data.range::<u8>(old_index, len))
|
||||||
|
{
|
||||||
|
*dst = src;
|
||||||
|
}
|
||||||
|
drop(old_data);
|
||||||
|
region.update(&new_data);
|
||||||
|
}
|
||||||
|
regions.insert(range, Arc::downgrade(®ion));
|
||||||
|
self.client
|
||||||
|
.send_command(EmulatorCommand::WatchMemory(range, Arc::downgrade(®ion)));
|
||||||
|
region
|
||||||
|
});
|
||||||
|
*view = MemoryView {
|
||||||
|
region,
|
||||||
|
sim,
|
||||||
|
start,
|
||||||
|
length,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write<T: MemoryValue>(&self, sim: SimId, address: u32, data: &T) {
|
pub fn write<T: MemoryValue>(&self, sim: SimId, address: u32, data: &T) {
|
||||||
@@ -64,12 +110,23 @@ fn aligned_memory(start: u32, length: usize) -> BoxBytes {
|
|||||||
|
|
||||||
pub struct MemoryView {
|
pub struct MemoryView {
|
||||||
region: Arc<MemoryRegion>,
|
region: Arc<MemoryRegion>,
|
||||||
|
sim: SimId,
|
||||||
|
start: u32,
|
||||||
|
length: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MemoryView {
|
impl MemoryView {
|
||||||
pub fn borrow(&self) -> MemoryRef<'_> {
|
pub fn borrow(&self) -> MemoryRef<'_> {
|
||||||
self.region.borrow()
|
self.region.borrow()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn range(&self) -> MemoryRange {
|
||||||
|
MemoryRange {
|
||||||
|
sim: self.sim,
|
||||||
|
start: self.start,
|
||||||
|
length: self.length,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct MemoryRef<'a> {
|
pub struct MemoryRef<'a> {
|
||||||
@@ -183,6 +240,14 @@ pub struct MemoryRange {
|
|||||||
pub length: usize,
|
pub length: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl MemoryRange {
|
||||||
|
pub fn overlaps(&self, other: MemoryRange) -> bool {
|
||||||
|
self.sim == other.sim
|
||||||
|
&& (self.start as usize) < other.start as usize + other.length
|
||||||
|
&& (other.start as usize) < self.start as usize + self.length
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const BUFFERS: usize = 4;
|
const BUFFERS: usize = 4;
|
||||||
pub struct MemoryRegion {
|
pub struct MemoryRegion {
|
||||||
gens: [AtomicU64; BUFFERS],
|
gens: [AtomicU64; BUFFERS],
|
||||||
@@ -195,10 +260,6 @@ impl Debug for MemoryRegion {
|
|||||||
.finish_non_exhaustive()
|
.finish_non_exhaustive()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// SAFETY: BoxBytes is meant to be Send+Sync, will be in a future version
|
|
||||||
unsafe impl Send for MemoryRegion {}
|
|
||||||
// SAFETY: BoxBytes is meant to be Send+Sync, will be in a future version
|
|
||||||
unsafe impl Sync for MemoryRegion {}
|
|
||||||
|
|
||||||
impl MemoryRegion {
|
impl MemoryRegion {
|
||||||
fn new(start: u32, length: usize) -> Self {
|
fn new(start: u32, length: usize) -> Self {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ pub use game_screen::{DisplayMode, GameScreen};
|
|||||||
pub use gdb::GdbServerWindow;
|
pub use gdb::GdbServerWindow;
|
||||||
pub use hotkeys::HotkeysWindow;
|
pub use hotkeys::HotkeysWindow;
|
||||||
pub use input::InputWindow;
|
pub use input::InputWindow;
|
||||||
|
pub use memory::MemoryWindow;
|
||||||
pub use profile::ProfileWindow;
|
pub use profile::ProfileWindow;
|
||||||
pub use terminal::TerminalWindow;
|
pub use terminal::TerminalWindow;
|
||||||
pub use vip::{
|
pub use vip::{
|
||||||
@@ -22,6 +23,7 @@ mod game_screen;
|
|||||||
mod gdb;
|
mod gdb;
|
||||||
mod hotkeys;
|
mod hotkeys;
|
||||||
mod input;
|
mod input;
|
||||||
|
mod memory;
|
||||||
mod profile;
|
mod profile;
|
||||||
mod terminal;
|
mod terminal;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|||||||
+44
-34
@@ -7,7 +7,7 @@ use std::{
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
app::UserEvent,
|
app::UserEvent,
|
||||||
config::{AppConfig, COLOR_PRESETS, SaveDataLocation, SimConfig},
|
config::{AppConfig, COLOR_PRESETS, Config, SaveDataLocation, SimConfig},
|
||||||
emulator::{EmulatorClient, EmulatorCommand, EmulatorState, SimId, SimState},
|
emulator::{EmulatorClient, EmulatorCommand, EmulatorState, SimId, SimState},
|
||||||
filepicker::FilePicker,
|
filepicker::FilePicker,
|
||||||
filesystem::Persistence,
|
filesystem::Persistence,
|
||||||
@@ -16,8 +16,8 @@ use crate::{
|
|||||||
memory::MemoryClient,
|
memory::MemoryClient,
|
||||||
window::{
|
window::{
|
||||||
AboutWindow, BgMapWindow, CharacterDataWindow, FrameBufferWindow, GdbServerWindow,
|
AboutWindow, BgMapWindow, CharacterDataWindow, FrameBufferWindow, GdbServerWindow,
|
||||||
HotkeysWindow, InitArgs, InputWindow, ObjectWindow, ProfileWindow, RegisterWindow,
|
HotkeysWindow, InitArgs, InputWindow, MemoryWindow, ObjectWindow, ProfileWindow,
|
||||||
TerminalWindow, WorldWindow, utils::UiData,
|
RegisterWindow, TerminalWindow, WorldWindow, utils::UiData,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use anyhow::Context as _;
|
use anyhow::Context as _;
|
||||||
@@ -42,7 +42,7 @@ pub struct GameWindow {
|
|||||||
persistence: Persistence,
|
persistence: Persistence,
|
||||||
shortcuts: ShortcutProvider,
|
shortcuts: ShortcutProvider,
|
||||||
sim_id: SimId,
|
sim_id: SimId,
|
||||||
config: SimConfig,
|
config: Config<SimConfig>,
|
||||||
toasts: Toasts,
|
toasts: Toasts,
|
||||||
screen: Option<GameScreen>,
|
screen: Option<GameScreen>,
|
||||||
messages: mpsc::Receiver<Toast>,
|
messages: mpsc::Receiver<Toast>,
|
||||||
@@ -124,25 +124,39 @@ impl GameWindow {
|
|||||||
ChildWindow::CharacterData => AppWrapper::of_dyn(CharacterDataWindow::new(
|
ChildWindow::CharacterData => AppWrapper::of_dyn(CharacterDataWindow::new(
|
||||||
self.sim_id,
|
self.sim_id,
|
||||||
&self.memory,
|
&self.memory,
|
||||||
|
&self.persistence,
|
||||||
|
&self.images,
|
||||||
|
)),
|
||||||
|
ChildWindow::BgMap => AppWrapper::of_dyn(BgMapWindow::new(
|
||||||
|
self.sim_id,
|
||||||
|
&self.memory,
|
||||||
|
&self.persistence,
|
||||||
|
&self.images,
|
||||||
|
)),
|
||||||
|
ChildWindow::Objects => AppWrapper::of_dyn(ObjectWindow::new(
|
||||||
|
self.sim_id,
|
||||||
|
&self.memory,
|
||||||
|
&self.persistence,
|
||||||
|
&self.images,
|
||||||
|
)),
|
||||||
|
ChildWindow::Worlds => AppWrapper::of_dyn(WorldWindow::new(
|
||||||
|
self.sim_id,
|
||||||
|
&self.memory,
|
||||||
|
&self.persistence,
|
||||||
&self.images,
|
&self.images,
|
||||||
)),
|
)),
|
||||||
ChildWindow::BgMap => {
|
|
||||||
AppWrapper::of_dyn(BgMapWindow::new(self.sim_id, &self.memory, &self.images))
|
|
||||||
}
|
|
||||||
ChildWindow::Objects => {
|
|
||||||
AppWrapper::of_dyn(ObjectWindow::new(self.sim_id, &self.memory, &self.images))
|
|
||||||
}
|
|
||||||
ChildWindow::Worlds => {
|
|
||||||
AppWrapper::of_dyn(WorldWindow::new(self.sim_id, &self.memory, &self.images))
|
|
||||||
}
|
|
||||||
ChildWindow::FrameBuffers => AppWrapper::of_dyn(FrameBufferWindow::new(
|
ChildWindow::FrameBuffers => AppWrapper::of_dyn(FrameBufferWindow::new(
|
||||||
self.sim_id,
|
self.sim_id,
|
||||||
&self.memory,
|
&self.memory,
|
||||||
|
&self.persistence,
|
||||||
&self.images,
|
&self.images,
|
||||||
)),
|
)),
|
||||||
ChildWindow::Registers => {
|
ChildWindow::Registers => AppWrapper::of_dyn(RegisterWindow::new(
|
||||||
AppWrapper::of_dyn(RegisterWindow::new(self.sim_id, &self.memory))
|
self.sim_id,
|
||||||
}
|
&self.memory,
|
||||||
|
&self.persistence,
|
||||||
|
)),
|
||||||
|
ChildWindow::Memory => AppWrapper::of_dyn(MemoryWindow::new(self.sim_id, &self.memory)),
|
||||||
ChildWindow::Terminal => {
|
ChildWindow::Terminal => {
|
||||||
AppWrapper::of_dyn(TerminalWindow::new(self.sim_id, &self.client))
|
AppWrapper::of_dyn(TerminalWindow::new(self.sim_id, &self.client))
|
||||||
}
|
}
|
||||||
@@ -334,7 +348,7 @@ impl GameWindow {
|
|||||||
}
|
}
|
||||||
let watch_rom = self.client.is_rom_watched(self.sim_id);
|
let watch_rom = self.client.is_rom_watched(self.sim_id);
|
||||||
if ui.selectable_button(watch_rom, "Watch ROM").clicked() {
|
if ui.selectable_button(watch_rom, "Watch ROM").clicked() {
|
||||||
self.update_config(|c| c.watch_rom = !watch_rom);
|
self.config.update(|c| c.watch_rom = !watch_rom);
|
||||||
self.client
|
self.client
|
||||||
.send_command(EmulatorCommand::WatchRom(self.sim_id, !watch_rom));
|
.send_command(EmulatorCommand::WatchRom(self.sim_id, !watch_rom));
|
||||||
}
|
}
|
||||||
@@ -395,7 +409,7 @@ impl GameWindow {
|
|||||||
ui.separator();
|
ui.separator();
|
||||||
let low_battery = self.config.low_battery;
|
let low_battery = self.config.low_battery;
|
||||||
if ui.selectable_button(low_battery, "Low Battery").clicked() {
|
if ui.selectable_button(low_battery, "Low Battery").clicked() {
|
||||||
self.update_config(|c| c.low_battery = !low_battery);
|
self.config.update(|c| c.low_battery = !low_battery);
|
||||||
let _ = self
|
let _ = self
|
||||||
.proxy
|
.proxy
|
||||||
.send_event(UserEvent::LowBattery(self.sim_id, !low_battery));
|
.send_event(UserEvent::LowBattery(self.sim_id, !low_battery));
|
||||||
@@ -432,6 +446,9 @@ impl GameWindow {
|
|||||||
if ui.button("GDB Server").clicked() {
|
if ui.button("GDB Server").clicked() {
|
||||||
self.open(ChildWindow::Debugger { port: None });
|
self.open(ChildWindow::Debugger { port: None });
|
||||||
}
|
}
|
||||||
|
if ui.button("Memory").clicked() {
|
||||||
|
self.open(ChildWindow::Memory);
|
||||||
|
}
|
||||||
ui.separator();
|
ui.separator();
|
||||||
if ui.button("Character Data").clicked() {
|
if ui.button("Character Data").clicked() {
|
||||||
self.open(ChildWindow::CharacterData);
|
self.open(ChildWindow::CharacterData);
|
||||||
@@ -551,7 +568,7 @@ impl GameWindow {
|
|||||||
ui.send_viewport_cmd(ViewportCommand::InnerSize(current_dims * scale));
|
ui.send_viewport_cmd(ViewportCommand::InnerSize(current_dims * scale));
|
||||||
}
|
}
|
||||||
|
|
||||||
self.update_config(|c| {
|
self.config.update(|c| {
|
||||||
c.display_mode = display_mode;
|
c.display_mode = display_mode;
|
||||||
c.dimensions = current_dims * scale;
|
c.dimensions = current_dims * scale;
|
||||||
});
|
});
|
||||||
@@ -559,7 +576,7 @@ impl GameWindow {
|
|||||||
ui.menu_button("Colors", |ui| {
|
ui.menu_button("Colors", |ui| {
|
||||||
for preset in COLOR_PRESETS {
|
for preset in COLOR_PRESETS {
|
||||||
if ui.color_pair_button(preset[0], preset[1]).clicked() {
|
if ui.color_pair_button(preset[0], preset[1]).clicked() {
|
||||||
self.update_config(|c| c.colors = preset);
|
self.config.update(|c| c.colors = preset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ui.with_layout(ui.layout().with_cross_align(egui::Align::Center), |ui| {
|
ui.with_layout(ui.layout().with_cross_align(egui::Align::Center), |ui| {
|
||||||
@@ -589,7 +606,7 @@ impl GameWindow {
|
|||||||
.selectable_button(self.config.audio_enabled, "Enabled")
|
.selectable_button(self.config.audio_enabled, "Enabled")
|
||||||
.clicked()
|
.clicked()
|
||||||
{
|
{
|
||||||
self.update_config(|c| c.audio_enabled = !c.audio_enabled);
|
self.config.update(|c| c.audio_enabled = !c.audio_enabled);
|
||||||
self.client.send_command(EmulatorCommand::SetAudioEnabled(
|
self.client.send_command(EmulatorCommand::SetAudioEnabled(
|
||||||
self.sim_id,
|
self.sim_id,
|
||||||
self.config.audio_enabled,
|
self.config.audio_enabled,
|
||||||
@@ -611,8 +628,7 @@ impl GameWindow {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let mut app_config = AppConfig::load(&self.persistence);
|
let mut app_config = AppConfig::load(&self.persistence);
|
||||||
app_config.save_data_location = new_location;
|
app_config.update(|c| c.save_data_location = new_location);
|
||||||
let _ = app_config.save(&self.persistence);
|
|
||||||
self.client
|
self.client
|
||||||
.send_command(EmulatorCommand::SetSaveDataLocation(new_location));
|
.send_command(EmulatorCommand::SetSaveDataLocation(new_location));
|
||||||
};
|
};
|
||||||
@@ -662,19 +678,10 @@ impl GameWindow {
|
|||||||
self.color_picker = None;
|
self.color_picker = None;
|
||||||
}
|
}
|
||||||
if updated {
|
if updated {
|
||||||
self.update_config(|c| c.colors = colors);
|
self.config.update(|c| c.colors = colors);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_config(&mut self, update: impl FnOnce(&mut SimConfig)) {
|
|
||||||
let mut new_config = self.config.clone();
|
|
||||||
update(&mut new_config);
|
|
||||||
if self.config != new_config {
|
|
||||||
let _ = new_config.save(&self.persistence, self.sim_id);
|
|
||||||
}
|
|
||||||
self.config = new_config;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn button_for(&self, ctx: &Context, text: &str, command: Command) -> Button<'_> {
|
fn button_for(&self, ctx: &Context, text: &str, command: Command) -> Button<'_> {
|
||||||
let button = Button::new(text);
|
let button = Button::new(text);
|
||||||
match self.shortcuts.shortcut_for(command) {
|
match self.shortcuts.shortcut_for(command) {
|
||||||
@@ -701,6 +708,7 @@ impl AppWindow for GameWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn show(&mut self, ui: &mut Ui) {
|
fn show(&mut self, ui: &mut Ui) {
|
||||||
|
self.config.refresh();
|
||||||
let has_player_2 = self.client.has_player_2();
|
let has_player_2 = self.client.has_player_2();
|
||||||
if !self.show_player_name && has_player_2 {
|
if !self.show_player_name && has_player_2 {
|
||||||
ui.send_viewport_cmd(ViewportCommand::Title(format!("Lemur ({})", self.sim_id)));
|
ui.send_viewport_cmd(ViewportCommand::Title(format!("Lemur ({})", self.sim_id)));
|
||||||
@@ -731,7 +739,7 @@ impl AppWindow for GameWindow {
|
|||||||
bounds.max - bounds.min
|
bounds.max - bounds.min
|
||||||
};
|
};
|
||||||
let position = ui.input(|i| i.viewport().outer_rect.map(|r| r.min));
|
let position = ui.input(|i| i.viewport().outer_rect.map(|r| r.min));
|
||||||
self.update_config(|c| {
|
self.config.update(|c| {
|
||||||
c.dimensions = dimensions;
|
c.dimensions = dimensions;
|
||||||
c.position = position;
|
c.position = position;
|
||||||
});
|
});
|
||||||
@@ -874,6 +882,7 @@ impl DerefMut for AppWrapper {
|
|||||||
pub enum ChildWindow {
|
pub enum ChildWindow {
|
||||||
About,
|
About,
|
||||||
CharacterData,
|
CharacterData,
|
||||||
|
Memory,
|
||||||
BgMap,
|
BgMap,
|
||||||
Objects,
|
Objects,
|
||||||
Worlds,
|
Worlds,
|
||||||
@@ -894,6 +903,7 @@ impl ChildWindow {
|
|||||||
match self {
|
match self {
|
||||||
Self::About => "About",
|
Self::About => "About",
|
||||||
Self::CharacterData => "CharacterData",
|
Self::CharacterData => "CharacterData",
|
||||||
|
Self::Memory => "Memory",
|
||||||
Self::BgMap => "BgMap",
|
Self::BgMap => "BgMap",
|
||||||
Self::Objects => "Objects",
|
Self::Objects => "Objects",
|
||||||
Self::Worlds => "Worlds",
|
Self::Worlds => "Worlds",
|
||||||
|
|||||||
@@ -0,0 +1,447 @@
|
|||||||
|
use std::{fmt::LowerHex, sync::Arc};
|
||||||
|
|
||||||
|
use egui::{
|
||||||
|
Align, CentralPanel, FontSelection, Label, Layout, Response, RichText, Separator, TextEdit, Ui,
|
||||||
|
ViewportBuilder, Widget, text::LayoutJob,
|
||||||
|
};
|
||||||
|
use egui_extras::{Column, Size, StripBuilder, TableBuilder};
|
||||||
|
use itertools::Itertools;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
emulator::SimId,
|
||||||
|
memory::{MemoryClient, MemoryRef, MemoryValue, MemoryView},
|
||||||
|
window::{AppWindow, utils::UiData},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
enum ValueSize {
|
||||||
|
Byte,
|
||||||
|
Halfword,
|
||||||
|
Word,
|
||||||
|
}
|
||||||
|
impl ValueSize {
|
||||||
|
fn size(self) -> usize {
|
||||||
|
match self {
|
||||||
|
Self::Byte => 1,
|
||||||
|
Self::Halfword => 2,
|
||||||
|
Self::Word => 4,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct HexEditResponse {
|
||||||
|
submitted: bool,
|
||||||
|
response: Response,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct HexEdit<'a> {
|
||||||
|
text: &'a mut String,
|
||||||
|
button_label: &'static str,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> HexEdit<'a> {
|
||||||
|
#[must_use]
|
||||||
|
fn new(text: &'a mut String, button_label: &'static str) -> Self {
|
||||||
|
Self { text, button_label }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn show(self, ui: &mut Ui) -> HexEditResponse {
|
||||||
|
const HEX_WORD_WIDTH: f32 = 80.0;
|
||||||
|
let text_response = TextEdit::singleline(self.text)
|
||||||
|
.desired_width(HEX_WORD_WIDTH)
|
||||||
|
.ui(ui);
|
||||||
|
let button_response = ui.button(self.button_label);
|
||||||
|
let submitted = (text_response.lost_focus()
|
||||||
|
&& text_response.ctx.input(|i| i.key_pressed(egui::Key::Enter)))
|
||||||
|
|| button_response.clicked();
|
||||||
|
let response = text_response.union(button_response);
|
||||||
|
HexEditResponse {
|
||||||
|
submitted,
|
||||||
|
response,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Widget for HexEdit<'_> {
|
||||||
|
fn ui(self, ui: &mut Ui) -> egui::Response {
|
||||||
|
self.show(ui).response
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
struct State {
|
||||||
|
address: u32,
|
||||||
|
value_size: ValueSize,
|
||||||
|
}
|
||||||
|
impl Default for State {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
address: 0x05000000,
|
||||||
|
value_size: ValueSize::Byte,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct MemoryWindow {
|
||||||
|
sim_id: SimId,
|
||||||
|
memory: Arc<MemoryClient>,
|
||||||
|
view: MemoryView,
|
||||||
|
scroll_to: Option<u32>,
|
||||||
|
jump_to: String,
|
||||||
|
editing_buffer: Vec<u8>,
|
||||||
|
state: UiData<State>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MemoryWindow {
|
||||||
|
pub fn new(sim_id: SimId, memory: &Arc<MemoryClient>) -> Self {
|
||||||
|
let state: UiData<State> = UiData::new();
|
||||||
|
Self {
|
||||||
|
sim_id,
|
||||||
|
memory: memory.clone(),
|
||||||
|
view: memory.watch(sim_id, 0x0, 0),
|
||||||
|
scroll_to: None,
|
||||||
|
jump_to: String::new(),
|
||||||
|
editing_buffer: vec![],
|
||||||
|
state,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn show_top_bar(&mut self, ui: &mut Ui) {
|
||||||
|
if ui
|
||||||
|
.radio_value(&mut self.state.value_size, ValueSize::Byte, "8-bit")
|
||||||
|
.clicked()
|
||||||
|
{
|
||||||
|
self.editing_buffer.clear();
|
||||||
|
}
|
||||||
|
if ui
|
||||||
|
.radio_value(&mut self.state.value_size, ValueSize::Halfword, "16-bit")
|
||||||
|
.clicked()
|
||||||
|
{
|
||||||
|
self.editing_buffer.clear();
|
||||||
|
}
|
||||||
|
if ui
|
||||||
|
.radio_value(&mut self.state.value_size, ValueSize::Word, "32-bit")
|
||||||
|
.clicked()
|
||||||
|
{
|
||||||
|
self.editing_buffer.clear();
|
||||||
|
}
|
||||||
|
self.state.address &= match self.state.value_size {
|
||||||
|
ValueSize::Byte => 0x07ffffff,
|
||||||
|
ValueSize::Halfword => 0x07fffffe,
|
||||||
|
ValueSize::Word => 0x07fffffc,
|
||||||
|
};
|
||||||
|
if HexEdit::new(&mut self.jump_to, "Jump to address")
|
||||||
|
.show(ui)
|
||||||
|
.submitted
|
||||||
|
{
|
||||||
|
let address_str = std::mem::take(&mut self.jump_to);
|
||||||
|
if let Ok(address) = u64::from_str_radix(&address_str, 16) {
|
||||||
|
let address = (address & 0x07ffffff) as u32;
|
||||||
|
self.state.address = address;
|
||||||
|
self.editing_buffer.clear();
|
||||||
|
self.scroll_to = Some(address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn show_memory(&mut self, ui: &mut Ui) {
|
||||||
|
let row_height_sans_spacing = ui.text_style_height(&egui::TextStyle::Monospace);
|
||||||
|
let max_height = ui.max_rect().height();
|
||||||
|
|
||||||
|
let something_has_focus = ui.memory(|m| m.focused().is_some());
|
||||||
|
let mut selected_changed = false;
|
||||||
|
ui.input(|i| {
|
||||||
|
if !i.focused || something_has_focus {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let arrow_keys: [(egui::Key, i32); 4] = [
|
||||||
|
(egui::Key::ArrowUp, -0x10),
|
||||||
|
(egui::Key::ArrowDown, 0x10),
|
||||||
|
(egui::Key::ArrowLeft, -(self.state.value_size.size() as i32)),
|
||||||
|
(egui::Key::ArrowRight, self.state.value_size.size() as i32),
|
||||||
|
];
|
||||||
|
for (key, delta) in arrow_keys {
|
||||||
|
if !selected_changed && i.key_pressed(key) {
|
||||||
|
self.state.address = self.state.address.wrapping_add_signed(delta) & 0x07ffffff;
|
||||||
|
self.editing_buffer.clear();
|
||||||
|
selected_changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !self.editing_buffer.is_empty() && i.key_pressed(egui::Key::Enter) {
|
||||||
|
let full_size = self.state.value_size.size() * 2;
|
||||||
|
while self.editing_buffer.len() < full_size {
|
||||||
|
self.editing_buffer.insert(0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut table = TableBuilder::new(ui)
|
||||||
|
.column(Column::auto())
|
||||||
|
.column(Column::initial(0.0))
|
||||||
|
.column(Column::auto())
|
||||||
|
.column(Column::initial(0.0))
|
||||||
|
.column(Column::remainder())
|
||||||
|
.max_scroll_height(max_height)
|
||||||
|
.animate_scrolling(false);
|
||||||
|
|
||||||
|
if let Some(scroll_to) = self.scroll_to.take() {
|
||||||
|
table = table.scroll_to_row(
|
||||||
|
(scroll_to as usize / 0x10).saturating_sub(2),
|
||||||
|
Some(Align::Min),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
table.body(|body| {
|
||||||
|
let total_rows = 0x00800000; // whole 27-bit non-mirrored address space, with 16 bytes per row
|
||||||
|
let range = self.view.range();
|
||||||
|
let current_start = range.start as usize;
|
||||||
|
let current_length = range.length;
|
||||||
|
let rows = self.view.borrow();
|
||||||
|
let mut new_start = None;
|
||||||
|
let mut new_length = 0;
|
||||||
|
|
||||||
|
body.rows(row_height_sans_spacing, total_rows, |mut row| {
|
||||||
|
let address = row.index() * 0x10;
|
||||||
|
let offset = address
|
||||||
|
.checked_sub(current_start)
|
||||||
|
.filter(|offset| offset + 0x10 <= current_length);
|
||||||
|
|
||||||
|
if new_start.is_none() {
|
||||||
|
new_start = Some(address);
|
||||||
|
}
|
||||||
|
new_length += 0x10;
|
||||||
|
row.col(|ui| {
|
||||||
|
let label = Label::new(RichText::new(format!("{address:08x}")).monospace());
|
||||||
|
if ui.add(label).clicked() {
|
||||||
|
self.state.address = address as u32;
|
||||||
|
self.editing_buffer.clear();
|
||||||
|
selected_changed = true;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
row.col(|ui| {
|
||||||
|
ui.separator();
|
||||||
|
});
|
||||||
|
row.col(|ui| {
|
||||||
|
if let Some(offset) = offset {
|
||||||
|
fn show_memory<T: MemoryValue + LowerHex>(
|
||||||
|
ui: &mut Ui,
|
||||||
|
rows: &MemoryRef,
|
||||||
|
(start, offset): (usize, usize),
|
||||||
|
selected: u32,
|
||||||
|
memory: &MemoryClient,
|
||||||
|
sim: SimId,
|
||||||
|
buffer: &mut Vec<u8>,
|
||||||
|
) -> Option<u32> {
|
||||||
|
let mut new_selected = None;
|
||||||
|
for (i, value) in rows
|
||||||
|
.range::<T>(offset / size_of::<T>(), 0x10 / size_of::<T>())
|
||||||
|
.enumerate()
|
||||||
|
{
|
||||||
|
let address = (start + offset + i * size_of::<T>()) as u32;
|
||||||
|
let mut job = LayoutJob::default();
|
||||||
|
|
||||||
|
let rich_text = |value: &str| {
|
||||||
|
let text = RichText::new(value).monospace();
|
||||||
|
if address == selected {
|
||||||
|
text.background_color(ui.visuals().selection.bg_fill)
|
||||||
|
.color(ui.visuals().selection.stroke.color)
|
||||||
|
} else {
|
||||||
|
text
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let edit_len = if address == selected { buffer.len() } else { 0 };
|
||||||
|
|
||||||
|
if edit_len > 0 {
|
||||||
|
let buffer_str =
|
||||||
|
buffer.iter().map(|d| format!("{d:x}")).join("");
|
||||||
|
let text = rich_text(&buffer_str).underline();
|
||||||
|
text.append_to(
|
||||||
|
&mut job,
|
||||||
|
ui.style(),
|
||||||
|
FontSelection::default(),
|
||||||
|
Align::Center,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let val_str = format!("{value:08x}")
|
||||||
|
.split_off(8 - size_of::<T>() * 2 + edit_len);
|
||||||
|
let text = rich_text(&val_str);
|
||||||
|
text.append_to(
|
||||||
|
&mut job,
|
||||||
|
ui.style(),
|
||||||
|
FontSelection::default(),
|
||||||
|
Align::Center,
|
||||||
|
);
|
||||||
|
|
||||||
|
let response = ui.label(job);
|
||||||
|
if response.clicked() {
|
||||||
|
new_selected = Some(address);
|
||||||
|
}
|
||||||
|
if ui.memory(|m| m.focused().is_none())
|
||||||
|
&& ui.input(|i| i.focused)
|
||||||
|
&& address == selected
|
||||||
|
{
|
||||||
|
const KEYS: [egui::Key; 16] = [
|
||||||
|
egui::Key::Num0,
|
||||||
|
egui::Key::Num1,
|
||||||
|
egui::Key::Num2,
|
||||||
|
egui::Key::Num3,
|
||||||
|
egui::Key::Num4,
|
||||||
|
egui::Key::Num5,
|
||||||
|
egui::Key::Num6,
|
||||||
|
egui::Key::Num7,
|
||||||
|
egui::Key::Num8,
|
||||||
|
egui::Key::Num9,
|
||||||
|
egui::Key::A,
|
||||||
|
egui::Key::B,
|
||||||
|
egui::Key::C,
|
||||||
|
egui::Key::D,
|
||||||
|
egui::Key::E,
|
||||||
|
egui::Key::F,
|
||||||
|
];
|
||||||
|
let pressed = ui.input(|i| {
|
||||||
|
for (index, key) in KEYS.into_iter().enumerate() {
|
||||||
|
if i.key_pressed(key) {
|
||||||
|
return Some(index as u8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
});
|
||||||
|
if let Some(value) = pressed {
|
||||||
|
buffer.push(value);
|
||||||
|
}
|
||||||
|
if buffer.len() == size_of::<T>() * 2 {
|
||||||
|
let bytes = buffer
|
||||||
|
.chunks(2)
|
||||||
|
.map(|letters| (letters[0] << 4) | letters[1])
|
||||||
|
.rev()
|
||||||
|
.collect::<Vec<u8>>();
|
||||||
|
let data = T::from_bytes(&bytes);
|
||||||
|
memory.write(sim, address, &data);
|
||||||
|
new_selected = Some(
|
||||||
|
address.wrapping_add(size_of::<T>() as u32)
|
||||||
|
& 0x07ffffff,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
new_selected
|
||||||
|
}
|
||||||
|
let selected = match self.state.value_size {
|
||||||
|
ValueSize::Byte => show_memory::<u8>(
|
||||||
|
ui,
|
||||||
|
&rows,
|
||||||
|
(current_start, offset),
|
||||||
|
self.state.address,
|
||||||
|
&self.memory,
|
||||||
|
self.sim_id,
|
||||||
|
&mut self.editing_buffer,
|
||||||
|
),
|
||||||
|
ValueSize::Halfword => show_memory::<u16>(
|
||||||
|
ui,
|
||||||
|
&rows,
|
||||||
|
(current_start, offset),
|
||||||
|
self.state.address,
|
||||||
|
&self.memory,
|
||||||
|
self.sim_id,
|
||||||
|
&mut self.editing_buffer,
|
||||||
|
),
|
||||||
|
ValueSize::Word => show_memory::<u32>(
|
||||||
|
ui,
|
||||||
|
&rows,
|
||||||
|
(current_start, offset),
|
||||||
|
self.state.address,
|
||||||
|
&self.memory,
|
||||||
|
self.sim_id,
|
||||||
|
&mut self.editing_buffer,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
if let Some(selected) = selected {
|
||||||
|
self.state.address = selected;
|
||||||
|
selected_changed = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if selected_changed {
|
||||||
|
self.editing_buffer.clear();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
row.col(|ui| {
|
||||||
|
ui.separator();
|
||||||
|
});
|
||||||
|
row.col(|ui| {
|
||||||
|
if let Some(offset) = offset {
|
||||||
|
let printable: String = rows
|
||||||
|
.range::<u8>(offset, 0x10)
|
||||||
|
.map(|c| {
|
||||||
|
if (32..128).contains(&c) {
|
||||||
|
c as char
|
||||||
|
} else {
|
||||||
|
'.'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
ui.label(RichText::new(printable).monospace());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
drop(rows);
|
||||||
|
if let Some(start) = new_start
|
||||||
|
&& (start != current_start || new_length != current_length)
|
||||||
|
{
|
||||||
|
self.memory
|
||||||
|
.retarget(&mut self.view, self.sim_id, start as u32, new_length);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if selected_changed && self.scroll_to.is_none() {
|
||||||
|
let range = self.view.range();
|
||||||
|
if self.state.address < range.start {
|
||||||
|
self.scroll_to = Some(self.state.address);
|
||||||
|
} else if let Some(delta) = self
|
||||||
|
.state
|
||||||
|
.address
|
||||||
|
.checked_sub(range.start + range.length as u32 - 0x20)
|
||||||
|
{
|
||||||
|
self.scroll_to = Some(range.start + delta + 0x40);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AppWindow for MemoryWindow {
|
||||||
|
fn sim_id(&self) -> Option<SimId> {
|
||||||
|
Some(self.sim_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn initial_viewport(&self) -> ViewportBuilder {
|
||||||
|
ViewportBuilder::default()
|
||||||
|
.with_title("Memory")
|
||||||
|
.with_inner_size((640.0, 480.0))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn show(&mut self, ui: &mut Ui) {
|
||||||
|
let row_height =
|
||||||
|
ui.text_style_height(&egui::TextStyle::Monospace) + ui.spacing().item_spacing.y;
|
||||||
|
let table_height = ui.available_height() - (row_height * 2.5);
|
||||||
|
if self.state.load(ui) {
|
||||||
|
self.scroll_to = Some(self.state.address);
|
||||||
|
}
|
||||||
|
CentralPanel::default().show(ui, |ui| {
|
||||||
|
StripBuilder::new(ui)
|
||||||
|
.size(Size::exact(row_height))
|
||||||
|
.size(Size::initial(0.0))
|
||||||
|
.size(Size::exact(table_height))
|
||||||
|
.cell_layout(Layout::left_to_right(Align::Center))
|
||||||
|
.vertical(|mut strip| {
|
||||||
|
strip.cell(|ui| self.show_top_bar(ui));
|
||||||
|
strip.cell(|ui| {
|
||||||
|
Separator::default().horizontal().ui(ui);
|
||||||
|
});
|
||||||
|
strip.cell(|ui| self.show_memory(ui));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
self.state.save(ui);
|
||||||
|
}
|
||||||
|
}
|
||||||
+3
-1
@@ -432,13 +432,15 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load(&mut self, ui: &Ui) {
|
pub fn load(&mut self, ui: &Ui) -> bool {
|
||||||
|
let just_loaded = !self.loaded;
|
||||||
if !self.loaded {
|
if !self.loaded {
|
||||||
self.current = ui
|
self.current = ui
|
||||||
.data_mut(|d| d.get_persisted(ui.id()))
|
.data_mut(|d| d.get_persisted(ui.id()))
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
self.loaded = true;
|
self.loaded = true;
|
||||||
}
|
}
|
||||||
|
just_loaded
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn save(&mut self, ui: &Ui) {
|
pub fn save(&mut self, ui: &Ui) {
|
||||||
|
|||||||
+23
-9
@@ -1,14 +1,16 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use egui::{
|
use egui::{
|
||||||
Align, CentralPanel, Checkbox, Color32, ComboBox, Image, ScrollArea, Slider, TextEdit,
|
Align, CentralPanel, Checkbox, ComboBox, Image, ScrollArea, Slider, TextEdit, TextureOptions,
|
||||||
TextureOptions, Ui, ViewportBuilder,
|
Ui, ViewportBuilder,
|
||||||
};
|
};
|
||||||
use egui_extras::{Column, Size, StripBuilder, TableBuilder};
|
use egui_extras::{Column, Size, StripBuilder, TableBuilder};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
config::{Config, SimConfig},
|
||||||
emulator::SimId,
|
emulator::SimId,
|
||||||
|
filesystem::Persistence,
|
||||||
images::{ImageBuffer, ImageParams, ImageRenderer, ImageTextureLoader},
|
images::{ImageBuffer, ImageParams, ImageRenderer, ImageTextureLoader},
|
||||||
memory::{MemoryClient, MemoryView},
|
memory::{MemoryClient, MemoryView},
|
||||||
window::{
|
window::{
|
||||||
@@ -46,9 +48,14 @@ pub struct BgMapWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl BgMapWindow {
|
impl BgMapWindow {
|
||||||
pub fn new(sim_id: SimId, memory: &Arc<MemoryClient>, images: &ImageTextureLoader) -> Self {
|
pub fn new(
|
||||||
|
sim_id: SimId,
|
||||||
|
memory: &Arc<MemoryClient>,
|
||||||
|
persistence: &Persistence,
|
||||||
|
images: &ImageTextureLoader,
|
||||||
|
) -> Self {
|
||||||
let state: UiData<State> = UiData::new();
|
let state: UiData<State> = UiData::new();
|
||||||
let renderer = BgMapRenderer::new(sim_id, memory);
|
let renderer = BgMapRenderer::new(sim_id, persistence, memory);
|
||||||
let params = images.add(
|
let params = images.add(
|
||||||
sim_id,
|
sim_id,
|
||||||
renderer,
|
renderer,
|
||||||
@@ -229,6 +236,7 @@ struct BgMapParams {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct BgMapRenderer {
|
struct BgMapRenderer {
|
||||||
|
config: Config<SimConfig>,
|
||||||
chardata: MemoryView,
|
chardata: MemoryView,
|
||||||
bgmaps: MemoryView,
|
bgmaps: MemoryView,
|
||||||
brightness: MemoryView,
|
brightness: MemoryView,
|
||||||
@@ -236,8 +244,9 @@ struct BgMapRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl BgMapRenderer {
|
impl BgMapRenderer {
|
||||||
pub fn new(sim_id: SimId, memory: &MemoryClient) -> Self {
|
pub fn new(sim_id: SimId, persistence: &Persistence, memory: &MemoryClient) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
config: SimConfig::load(persistence, sim_id),
|
||||||
chardata: memory.watch(sim_id, 0x00078000, 0x8000),
|
chardata: memory.watch(sim_id, 0x00078000, 0x8000),
|
||||||
bgmaps: memory.watch(sim_id, 0x00020000, 0x20000),
|
bgmaps: memory.watch(sim_id, 0x00020000, 0x20000),
|
||||||
brightness: memory.watch(sim_id, 0x0005f824, 8),
|
brightness: memory.watch(sim_id, 0x0005f824, 8),
|
||||||
@@ -246,6 +255,8 @@ impl BgMapRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn render_bgmap(&self, image: &mut ImageBuffer, bgmap_index: usize, generic_palette: bool) {
|
fn render_bgmap(&self, image: &mut ImageBuffer, bgmap_index: usize, generic_palette: bool) {
|
||||||
|
let color = self.config.colors[0];
|
||||||
|
|
||||||
let chardata = self.chardata.borrow();
|
let chardata = self.chardata.borrow();
|
||||||
let bgmaps = self.bgmaps.borrow();
|
let bgmaps = self.bgmaps.borrow();
|
||||||
let brightness = self.brightness.borrow();
|
let brightness = self.brightness.borrow();
|
||||||
@@ -253,9 +264,9 @@ impl BgMapRenderer {
|
|||||||
|
|
||||||
let brts = brightness.read::<[u8; 8]>(0);
|
let brts = brightness.read::<[u8; 8]>(0);
|
||||||
let colors = if generic_palette {
|
let colors = if generic_palette {
|
||||||
[utils::generic_palette(Color32::RED); 4]
|
[utils::generic_palette(color); 4]
|
||||||
} else {
|
} else {
|
||||||
[0, 2, 4, 6].map(|i| utils::palette_colors(palettes.read(i), &brts, Color32::RED))
|
[0, 2, 4, 6].map(|i| utils::palette_colors(palettes.read(i), &brts, color))
|
||||||
};
|
};
|
||||||
|
|
||||||
for (i, cell) in bgmaps.range::<u16>(bgmap_index * 4096, 4096).enumerate() {
|
for (i, cell) in bgmaps.range::<u16>(bgmap_index * 4096, 4096).enumerate() {
|
||||||
@@ -279,6 +290,8 @@ impl BgMapRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn render_bgmap_cell(&self, image: &mut ImageBuffer, index: usize, generic_palette: bool) {
|
fn render_bgmap_cell(&self, image: &mut ImageBuffer, index: usize, generic_palette: bool) {
|
||||||
|
let color = self.config.colors[0];
|
||||||
|
|
||||||
let chardata = self.chardata.borrow();
|
let chardata = self.chardata.borrow();
|
||||||
let bgmaps = self.bgmaps.borrow();
|
let bgmaps = self.bgmaps.borrow();
|
||||||
let brightness = self.brightness.borrow();
|
let brightness = self.brightness.borrow();
|
||||||
@@ -296,9 +309,9 @@ impl BgMapRenderer {
|
|||||||
} = CellData::parse(cell);
|
} = CellData::parse(cell);
|
||||||
let char = chardata.read::<[u16; 8]>(char_index);
|
let char = chardata.read::<[u16; 8]>(char_index);
|
||||||
let palette = if generic_palette {
|
let palette = if generic_palette {
|
||||||
utils::generic_palette(Color32::RED)
|
utils::generic_palette(color)
|
||||||
} else {
|
} else {
|
||||||
utils::palette_colors(palettes.read(palette_index * 2), &brts, Color32::RED)
|
utils::palette_colors(palettes.read(palette_index * 2), &brts, color)
|
||||||
};
|
};
|
||||||
|
|
||||||
for row in 0..8 {
|
for row in 0..8 {
|
||||||
@@ -321,6 +334,7 @@ impl ImageRenderer<2> for BgMapRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn render(&mut self, params: &Self::Params, images: &mut [ImageBuffer; 2]) {
|
fn render(&mut self, params: &Self::Params, images: &mut [ImageBuffer; 2]) {
|
||||||
|
self.config.refresh();
|
||||||
self.render_bgmap_cell(&mut images[0], params.cell_index, params.generic_palette);
|
self.render_bgmap_cell(&mut images[0], params.cell_index, params.generic_palette);
|
||||||
self.render_bgmap(
|
self.render_bgmap(
|
||||||
&mut images[1],
|
&mut images[1],
|
||||||
|
|||||||
+33
-13
@@ -8,7 +8,9 @@ use egui_extras::{Column, Size, StripBuilder, TableBuilder};
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
config::{Config, SimConfig},
|
||||||
emulator::SimId,
|
emulator::SimId,
|
||||||
|
filesystem::Persistence,
|
||||||
images::{ImageBuffer, ImageParams, ImageRenderer, ImageTextureLoader},
|
images::{ImageBuffer, ImageParams, ImageRenderer, ImageTextureLoader},
|
||||||
memory::{MemoryClient, MemoryView},
|
memory::{MemoryClient, MemoryView},
|
||||||
window::{
|
window::{
|
||||||
@@ -99,6 +101,7 @@ impl Default for State {
|
|||||||
|
|
||||||
pub struct CharacterDataWindow {
|
pub struct CharacterDataWindow {
|
||||||
sim_id: SimId,
|
sim_id: SimId,
|
||||||
|
config: Config<SimConfig>,
|
||||||
brightness: MemoryView,
|
brightness: MemoryView,
|
||||||
palettes: MemoryView,
|
palettes: MemoryView,
|
||||||
params: ImageParams<CharDataParams>,
|
params: ImageParams<CharDataParams>,
|
||||||
@@ -106,7 +109,13 @@ pub struct CharacterDataWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl CharacterDataWindow {
|
impl CharacterDataWindow {
|
||||||
pub fn new(sim_id: SimId, memory: &MemoryClient, images: &ImageTextureLoader) -> Self {
|
pub fn new(
|
||||||
|
sim_id: SimId,
|
||||||
|
memory: &MemoryClient,
|
||||||
|
persistence: &Persistence,
|
||||||
|
images: &ImageTextureLoader,
|
||||||
|
) -> Self {
|
||||||
|
let config = SimConfig::load(persistence, sim_id);
|
||||||
let state: UiData<State> = UiData::new();
|
let state: UiData<State> = UiData::new();
|
||||||
let renderer = CharDataRenderer::new(sim_id, memory);
|
let renderer = CharDataRenderer::new(sim_id, memory);
|
||||||
let params = images.add(
|
let params = images.add(
|
||||||
@@ -115,10 +124,12 @@ impl CharacterDataWindow {
|
|||||||
CharDataParams {
|
CharDataParams {
|
||||||
palette: state.palette,
|
palette: state.palette,
|
||||||
index: state.index,
|
index: state.index,
|
||||||
|
color: config.colors[0],
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
Self {
|
Self {
|
||||||
sim_id,
|
sim_id,
|
||||||
|
config,
|
||||||
brightness: memory.watch(sim_id, 0x0005f824, 8),
|
brightness: memory.watch(sim_id, 0x0005f824, 8),
|
||||||
palettes: memory.watch(sim_id, 0x0005f860, 16),
|
palettes: memory.watch(sim_id, 0x0005f860, 16),
|
||||||
params,
|
params,
|
||||||
@@ -226,17 +237,19 @@ impl CharacterDataWindow {
|
|||||||
self.params.write(CharDataParams {
|
self.params.write(CharDataParams {
|
||||||
palette: self.state.palette,
|
palette: self.state.palette,
|
||||||
index: self.state.index,
|
index: self.state.index,
|
||||||
|
color: self.config.colors[0],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_palette_colors(&self) -> [Color32; 4] {
|
fn load_palette_colors(&self) -> [Color32; 4] {
|
||||||
|
let color = self.config.colors[0];
|
||||||
let Some(offset) = self.state.palette.offset() else {
|
let Some(offset) = self.state.palette.offset() else {
|
||||||
return utils::generic_palette(Color32::RED);
|
return utils::generic_palette(color);
|
||||||
};
|
};
|
||||||
let palette = self.palettes.borrow().read(offset);
|
let palette = self.palettes.borrow().read(offset);
|
||||||
let brightnesses = self.brightness.borrow();
|
let brightnesses = self.brightness.borrow();
|
||||||
let brts = brightnesses.read(0);
|
let brts = brightnesses.read(0);
|
||||||
utils::palette_colors(palette, &brts, Color32::RED)
|
utils::palette_colors(palette, &brts, color)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn show_chardata(&mut self, ui: &mut Ui) {
|
fn show_chardata(&mut self, ui: &mut Ui) {
|
||||||
@@ -262,6 +275,7 @@ impl AppWindow for CharacterDataWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn show(&mut self, ui: &mut Ui) {
|
fn show(&mut self, ui: &mut Ui) {
|
||||||
|
self.config.refresh();
|
||||||
self.state.load(ui);
|
self.state.load(ui);
|
||||||
CentralPanel::default().show(ui, |ui| {
|
CentralPanel::default().show(ui, |ui| {
|
||||||
ui.horizontal_top(|ui| {
|
ui.horizontal_top(|ui| {
|
||||||
@@ -286,8 +300,8 @@ impl AppWindow for CharacterDataWindow {
|
|||||||
struct CharDataParams {
|
struct CharDataParams {
|
||||||
palette: Palette,
|
palette: Palette,
|
||||||
index: usize,
|
index: usize,
|
||||||
|
color: Color32,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct CharDataRenderer {
|
struct CharDataRenderer {
|
||||||
chardata: MemoryView,
|
chardata: MemoryView,
|
||||||
brightness: MemoryView,
|
brightness: MemoryView,
|
||||||
@@ -306,8 +320,8 @@ impl ImageRenderer<2> for CharDataRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn render(&mut self, params: &Self::Params, image: &mut [ImageBuffer; 2]) {
|
fn render(&mut self, params: &Self::Params, image: &mut [ImageBuffer; 2]) {
|
||||||
self.render_character(&mut image[0], params.palette, params.index);
|
self.render_character(&mut image[0], params.palette, params.index, params.color);
|
||||||
self.render_character_data(&mut image[1], params.palette);
|
self.render_character_data(&mut image[1], params.palette, params.color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -320,11 +334,17 @@ impl CharDataRenderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_character(&self, image: &mut ImageBuffer, palette: Palette, index: usize) {
|
fn render_character(
|
||||||
|
&self,
|
||||||
|
image: &mut ImageBuffer,
|
||||||
|
palette: Palette,
|
||||||
|
index: usize,
|
||||||
|
color: Color32,
|
||||||
|
) {
|
||||||
if index >= 2048 {
|
if index >= 2048 {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let palette = self.load_palette(palette);
|
let palette = self.load_palette(palette, color);
|
||||||
let chardata = self.chardata.borrow();
|
let chardata = self.chardata.borrow();
|
||||||
let character = chardata.range::<u16>(index * 8, 8);
|
let character = chardata.range::<u16>(index * 8, 8);
|
||||||
for (row, pixels) in character.enumerate() {
|
for (row, pixels) in character.enumerate() {
|
||||||
@@ -335,8 +355,8 @@ impl CharDataRenderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_character_data(&self, image: &mut ImageBuffer, palette: Palette) {
|
fn render_character_data(&self, image: &mut ImageBuffer, palette: Palette, color: Color32) {
|
||||||
let palette = self.load_palette(palette);
|
let palette = self.load_palette(palette, color);
|
||||||
let chardata = self.chardata.borrow();
|
let chardata = self.chardata.borrow();
|
||||||
for (row, pixels) in chardata.range::<u16>(0, 8 * 2048).enumerate() {
|
for (row, pixels) in chardata.range::<u16>(0, 8 * 2048).enumerate() {
|
||||||
let char_index = row / 8;
|
let char_index = row / 8;
|
||||||
@@ -350,13 +370,13 @@ impl CharDataRenderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_palette(&self, palette: Palette) -> [Color32; 4] {
|
fn load_palette(&self, palette: Palette, color: Color32) -> [Color32; 4] {
|
||||||
let Some(offset) = palette.offset() else {
|
let Some(offset) = palette.offset() else {
|
||||||
return utils::GENERIC_PALETTE.map(|p| utils::shade(p, Color32::RED));
|
return utils::GENERIC_PALETTE.map(|p| utils::shade(p, color));
|
||||||
};
|
};
|
||||||
let palette = self.palettes.borrow().read(offset);
|
let palette = self.palettes.borrow().read(offset);
|
||||||
let brightnesses = self.brightness.borrow();
|
let brightnesses = self.brightness.borrow();
|
||||||
let brts = brightnesses.read(0);
|
let brts = brightnesses.read(0);
|
||||||
utils::palette_colors(palette, &brts, Color32::RED)
|
utils::palette_colors(palette, &brts, color)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,9 @@ use egui_extras::{Column, Size, StripBuilder, TableBuilder};
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
config::{Config, SimConfig},
|
||||||
emulator::SimId,
|
emulator::SimId,
|
||||||
|
filesystem::Persistence,
|
||||||
images::{ImageBuffer, ImageParams, ImageRenderer, ImageTextureLoader},
|
images::{ImageBuffer, ImageParams, ImageRenderer, ImageTextureLoader},
|
||||||
memory::{MemoryClient, MemoryView},
|
memory::{MemoryClient, MemoryView},
|
||||||
window::{
|
window::{
|
||||||
@@ -44,9 +46,14 @@ pub struct FrameBufferWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl FrameBufferWindow {
|
impl FrameBufferWindow {
|
||||||
pub fn new(sim_id: SimId, memory: &MemoryClient, images: &ImageTextureLoader) -> Self {
|
pub fn new(
|
||||||
|
sim_id: SimId,
|
||||||
|
memory: &MemoryClient,
|
||||||
|
persistence: &Persistence,
|
||||||
|
images: &ImageTextureLoader,
|
||||||
|
) -> Self {
|
||||||
let state: UiData<State> = UiData::new();
|
let state: UiData<State> = UiData::new();
|
||||||
let renderer = FrameBufferRenderer::new(sim_id, memory);
|
let renderer = FrameBufferRenderer::new(sim_id, memory, persistence);
|
||||||
let params = images.add(
|
let params = images.add(
|
||||||
sim_id,
|
sim_id,
|
||||||
renderer,
|
renderer,
|
||||||
@@ -55,8 +62,6 @@ impl FrameBufferWindow {
|
|||||||
left: state.left,
|
left: state.left,
|
||||||
right: state.right,
|
right: state.right,
|
||||||
generic_palette: state.generic_palette,
|
generic_palette: state.generic_palette,
|
||||||
left_color: Color32::from_rgb(0xff, 0x00, 0x00),
|
|
||||||
right_color: Color32::from_rgb(0x00, 0xc6, 0xf0),
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
Self {
|
Self {
|
||||||
@@ -138,7 +143,6 @@ impl FrameBufferWindow {
|
|||||||
left: self.state.left,
|
left: self.state.left,
|
||||||
right: self.state.right,
|
right: self.state.right,
|
||||||
generic_palette: self.state.generic_palette,
|
generic_palette: self.state.generic_palette,
|
||||||
..*self.params
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,18 +192,18 @@ struct FrameBufferParams {
|
|||||||
left: bool,
|
left: bool,
|
||||||
right: bool,
|
right: bool,
|
||||||
generic_palette: bool,
|
generic_palette: bool,
|
||||||
left_color: Color32,
|
|
||||||
right_color: Color32,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct FrameBufferRenderer {
|
struct FrameBufferRenderer {
|
||||||
|
config: Config<SimConfig>,
|
||||||
buffers: [MemoryView; 4],
|
buffers: [MemoryView; 4],
|
||||||
brightness: MemoryView,
|
brightness: MemoryView,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FrameBufferRenderer {
|
impl FrameBufferRenderer {
|
||||||
fn new(sim_id: SimId, memory: &MemoryClient) -> Self {
|
fn new(sim_id: SimId, memory: &MemoryClient, persistence: &Persistence) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
config: SimConfig::load(persistence, sim_id),
|
||||||
buffers: [
|
buffers: [
|
||||||
memory.watch(sim_id, 0x00000000, 0x6000),
|
memory.watch(sim_id, 0x00000000, 0x6000),
|
||||||
memory.watch(sim_id, 0x00008000, 0x6000),
|
memory.watch(sim_id, 0x00008000, 0x6000),
|
||||||
@@ -223,6 +227,7 @@ impl ImageRenderer<1> for FrameBufferRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn render(&mut self, params: &Self::Params, images: &mut [ImageBuffer; 1]) {
|
fn render(&mut self, params: &Self::Params, images: &mut [ImageBuffer; 1]) {
|
||||||
|
self.config.refresh();
|
||||||
let image = &mut images[0];
|
let image = &mut images[0];
|
||||||
|
|
||||||
let left_buffer = self.buffers[params.index].borrow();
|
let left_buffer = self.buffers[params.index].borrow();
|
||||||
@@ -230,15 +235,15 @@ impl ImageRenderer<1> for FrameBufferRenderer {
|
|||||||
|
|
||||||
let colors = if params.generic_palette {
|
let colors = if params.generic_palette {
|
||||||
[
|
[
|
||||||
utils::generic_palette(params.left_color),
|
utils::generic_palette(self.config.colors[0]),
|
||||||
utils::generic_palette(params.right_color),
|
utils::generic_palette(self.config.colors[1]),
|
||||||
]
|
]
|
||||||
} else {
|
} else {
|
||||||
let brts = self.brightness.borrow().read::<[u8; 8]>(0);
|
let brts = self.brightness.borrow().read::<[u8; 8]>(0);
|
||||||
let shades = utils::parse_shades(&brts);
|
let shades = utils::parse_shades(&brts);
|
||||||
[
|
[
|
||||||
shades.map(|s| utils::shade(s, params.left_color)),
|
shades.map(|s| utils::shade(s, self.config.colors[0])),
|
||||||
shades.map(|s| utils::shade(s, params.right_color)),
|
shades.map(|s| utils::shade(s, self.config.colors[1])),
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -261,11 +266,7 @@ impl ImageRenderer<1> for FrameBufferRenderer {
|
|||||||
} else {
|
} else {
|
||||||
Color32::BLACK
|
Color32::BLACK
|
||||||
};
|
};
|
||||||
Color32::from_rgb(
|
left + right
|
||||||
left.r() + right.r(),
|
|
||||||
left.g() + right.g(),
|
|
||||||
left.b() + right.b(),
|
|
||||||
)
|
|
||||||
});
|
});
|
||||||
let x = index / 64;
|
let x = index / 64;
|
||||||
for (i, pixel) in pixels.into_iter().enumerate() {
|
for (i, pixel) in pixels.into_iter().enumerate() {
|
||||||
|
|||||||
+17
-12
@@ -1,14 +1,16 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use egui::{
|
use egui::{
|
||||||
Align, CentralPanel, Checkbox, Color32, ComboBox, Image, ScrollArea, Slider, TextEdit,
|
Align, CentralPanel, Checkbox, ComboBox, Image, ScrollArea, Slider, TextEdit, TextureOptions,
|
||||||
TextureOptions, Ui, ViewportBuilder,
|
Ui, ViewportBuilder,
|
||||||
};
|
};
|
||||||
use egui_extras::{Column, Size, StripBuilder, TableBuilder};
|
use egui_extras::{Column, Size, StripBuilder, TableBuilder};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
config::{Config, SimConfig},
|
||||||
emulator::SimId,
|
emulator::SimId,
|
||||||
|
filesystem::Persistence,
|
||||||
images::{ImageBuffer, ImageParams, ImageRenderer, ImageTextureLoader},
|
images::{ImageBuffer, ImageParams, ImageRenderer, ImageTextureLoader},
|
||||||
memory::{MemoryClient, MemoryView},
|
memory::{MemoryClient, MemoryView},
|
||||||
window::{
|
window::{
|
||||||
@@ -44,17 +46,20 @@ pub struct ObjectWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ObjectWindow {
|
impl ObjectWindow {
|
||||||
pub fn new(sim_id: SimId, memory: &Arc<MemoryClient>, images: &ImageTextureLoader) -> Self {
|
pub fn new(
|
||||||
|
sim_id: SimId,
|
||||||
|
memory: &Arc<MemoryClient>,
|
||||||
|
persistence: &Persistence,
|
||||||
|
images: &ImageTextureLoader,
|
||||||
|
) -> Self {
|
||||||
let state: UiData<State> = UiData::new();
|
let state: UiData<State> = UiData::new();
|
||||||
let renderer = ObjectRenderer::new(sim_id, memory);
|
let renderer = ObjectRenderer::new(sim_id, memory, persistence);
|
||||||
let params = images.add(
|
let params = images.add(
|
||||||
sim_id,
|
sim_id,
|
||||||
renderer,
|
renderer,
|
||||||
ObjectParams {
|
ObjectParams {
|
||||||
index: state.index,
|
index: state.index,
|
||||||
generic_palette: state.generic_palette,
|
generic_palette: state.generic_palette,
|
||||||
left_color: Color32::from_rgb(0xff, 0x00, 0x00),
|
|
||||||
right_color: Color32::from_rgb(0x00, 0xc6, 0xf0),
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
Self {
|
Self {
|
||||||
@@ -194,7 +199,6 @@ impl ObjectWindow {
|
|||||||
self.params.write(ObjectParams {
|
self.params.write(ObjectParams {
|
||||||
index: self.state.index,
|
index: self.state.index,
|
||||||
generic_palette: self.state.generic_palette,
|
generic_palette: self.state.generic_palette,
|
||||||
..*self.params
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -242,8 +246,6 @@ impl AppWindow for ObjectWindow {
|
|||||||
struct ObjectParams {
|
struct ObjectParams {
|
||||||
index: usize,
|
index: usize,
|
||||||
generic_palette: bool,
|
generic_palette: bool,
|
||||||
left_color: Color32,
|
|
||||||
right_color: Color32,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Eye {
|
enum Eye {
|
||||||
@@ -252,6 +254,7 @@ enum Eye {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct ObjectRenderer {
|
struct ObjectRenderer {
|
||||||
|
config: Config<SimConfig>,
|
||||||
chardata: MemoryView,
|
chardata: MemoryView,
|
||||||
objects: MemoryView,
|
objects: MemoryView,
|
||||||
brightness: MemoryView,
|
brightness: MemoryView,
|
||||||
@@ -259,8 +262,9 @@ struct ObjectRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ObjectRenderer {
|
impl ObjectRenderer {
|
||||||
pub fn new(sim_id: SimId, memory: &MemoryClient) -> Self {
|
pub fn new(sim_id: SimId, memory: &MemoryClient, persistence: &Persistence) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
config: SimConfig::load(persistence, sim_id),
|
||||||
chardata: memory.watch(sim_id, 0x00078000, 0x8000),
|
chardata: memory.watch(sim_id, 0x00078000, 0x8000),
|
||||||
objects: memory.watch(sim_id, 0x0003e000, 0x2000),
|
objects: memory.watch(sim_id, 0x0003e000, 0x2000),
|
||||||
brightness: memory.watch(sim_id, 0x0005f824, 8),
|
brightness: memory.watch(sim_id, 0x0005f824, 8),
|
||||||
@@ -302,8 +306,8 @@ impl ObjectRenderer {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let color = match eye {
|
let color = match eye {
|
||||||
Eye::Left => params.left_color,
|
Eye::Left => self.config.colors[0],
|
||||||
Eye::Right => params.right_color,
|
Eye::Right => self.config.colors[1],
|
||||||
};
|
};
|
||||||
|
|
||||||
let char = chardata.read::<[u16; 8]>(obj.data.char_index);
|
let char = chardata.read::<[u16; 8]>(obj.data.char_index);
|
||||||
@@ -343,6 +347,7 @@ impl ImageRenderer<2> for ObjectRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn render(&mut self, params: &Self::Params, images: &mut [ImageBuffer; 2]) {
|
fn render(&mut self, params: &Self::Params, images: &mut [ImageBuffer; 2]) {
|
||||||
|
self.config.refresh();
|
||||||
images[0].clear();
|
images[0].clear();
|
||||||
self.render_object(&mut images[0], params, false, Eye::Left);
|
self.render_object(&mut images[0], params, false, Eye::Left);
|
||||||
self.render_object(&mut images[0], params, false, Eye::Right);
|
self.render_object(&mut images[0], params, false, Eye::Right);
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use egui::{
|
use egui::{
|
||||||
Align, Button, CentralPanel, Checkbox, Color32, Direction, Label, Layout, ScrollArea, TextEdit,
|
Align, Button, CentralPanel, Checkbox, Direction, Label, Layout, ScrollArea, TextEdit, Ui,
|
||||||
Ui, ViewportBuilder,
|
ViewportBuilder,
|
||||||
};
|
};
|
||||||
use egui_extras::{Column, Size, StripBuilder, TableBuilder};
|
use egui_extras::{Column, Size, StripBuilder, TableBuilder};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
config::{Config, SimConfig},
|
||||||
emulator::SimId,
|
emulator::SimId,
|
||||||
|
filesystem::Persistence,
|
||||||
memory::{MemoryClient, MemoryRef, MemoryValue, MemoryView},
|
memory::{MemoryClient, MemoryRef, MemoryValue, MemoryView},
|
||||||
window::{
|
window::{
|
||||||
AppWindow,
|
AppWindow,
|
||||||
@@ -19,14 +21,16 @@ use super::utils;
|
|||||||
|
|
||||||
pub struct RegisterWindow {
|
pub struct RegisterWindow {
|
||||||
sim_id: SimId,
|
sim_id: SimId,
|
||||||
|
config: Config<SimConfig>,
|
||||||
memory: Arc<MemoryClient>,
|
memory: Arc<MemoryClient>,
|
||||||
registers: MemoryView,
|
registers: MemoryView,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RegisterWindow {
|
impl RegisterWindow {
|
||||||
pub fn new(sim_id: SimId, memory: &Arc<MemoryClient>) -> Self {
|
pub fn new(sim_id: SimId, memory: &Arc<MemoryClient>, persistence: &Persistence) -> Self {
|
||||||
Self {
|
Self {
|
||||||
sim_id,
|
sim_id,
|
||||||
|
config: SimConfig::load(persistence, sim_id),
|
||||||
memory: memory.clone(),
|
memory: memory.clone(),
|
||||||
registers: memory.watch(sim_id, 0x0005f800, 0x72),
|
registers: memory.watch(sim_id, 0x0005f800, 0x72),
|
||||||
}
|
}
|
||||||
@@ -366,7 +370,7 @@ impl RegisterWindow {
|
|||||||
});
|
});
|
||||||
body.row(row_height, |mut row| {
|
body.row(row_height, |mut row| {
|
||||||
row.col(|_ui| {});
|
row.col(|_ui| {});
|
||||||
for shade in utils::parse_brts(&brts, Color32::RED) {
|
for shade in utils::parse_brts(&brts, self.config.colors[0]) {
|
||||||
row.col(|ui| {
|
row.col(|ui| {
|
||||||
ui.painter().rect_filled(
|
ui.painter().rect_filled(
|
||||||
ui.available_rect_before_wrap(),
|
ui.available_rect_before_wrap(),
|
||||||
@@ -641,6 +645,7 @@ impl AppWindow for RegisterWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn show(&mut self, ui: &mut Ui) {
|
fn show(&mut self, ui: &mut Ui) {
|
||||||
|
self.config.refresh();
|
||||||
CentralPanel::default().show(ui, |ui| {
|
CentralPanel::default().show(ui, |ui| {
|
||||||
ScrollArea::vertical().show(ui, |ui| {
|
ScrollArea::vertical().show(ui, |ui| {
|
||||||
ui.horizontal_top(|ui| {
|
ui.horizontal_top(|ui| {
|
||||||
|
|||||||
+21
-14
@@ -14,7 +14,9 @@ use num_traits::{FromPrimitive, ToPrimitive};
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
config::{Config, SimConfig},
|
||||||
emulator::SimId,
|
emulator::SimId,
|
||||||
|
filesystem::Persistence,
|
||||||
images::{ImageBuffer, ImageParams, ImageRenderer, ImageTextureLoader},
|
images::{ImageBuffer, ImageParams, ImageRenderer, ImageTextureLoader},
|
||||||
memory::{MemoryClient, MemoryRef, MemoryView},
|
memory::{MemoryClient, MemoryRef, MemoryView},
|
||||||
window::{
|
window::{
|
||||||
@@ -47,6 +49,7 @@ impl Default for State {
|
|||||||
|
|
||||||
pub struct WorldWindow {
|
pub struct WorldWindow {
|
||||||
sim_id: SimId,
|
sim_id: SimId,
|
||||||
|
config: Config<SimConfig>,
|
||||||
memory: Arc<MemoryClient>,
|
memory: Arc<MemoryClient>,
|
||||||
worlds: MemoryView,
|
worlds: MemoryView,
|
||||||
bgmaps: MemoryView,
|
bgmaps: MemoryView,
|
||||||
@@ -55,7 +58,13 @@ pub struct WorldWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl WorldWindow {
|
impl WorldWindow {
|
||||||
pub fn new(sim_id: SimId, memory: &Arc<MemoryClient>, images: &ImageTextureLoader) -> Self {
|
pub fn new(
|
||||||
|
sim_id: SimId,
|
||||||
|
memory: &Arc<MemoryClient>,
|
||||||
|
persistence: &Persistence,
|
||||||
|
images: &ImageTextureLoader,
|
||||||
|
) -> Self {
|
||||||
|
let config = SimConfig::load(persistence, sim_id);
|
||||||
let state: UiData<State> = UiData::new();
|
let state: UiData<State> = UiData::new();
|
||||||
let renderer = WorldRenderer::new(sim_id, memory);
|
let renderer = WorldRenderer::new(sim_id, memory);
|
||||||
let params = images.add(
|
let params = images.add(
|
||||||
@@ -64,12 +73,13 @@ impl WorldWindow {
|
|||||||
WorldParams {
|
WorldParams {
|
||||||
index: state.index,
|
index: state.index,
|
||||||
generic_palette: state.generic_palette,
|
generic_palette: state.generic_palette,
|
||||||
left_color: Color32::from_rgb(0xff, 0x00, 0x00),
|
left_color: config.colors[0],
|
||||||
right_color: Color32::from_rgb(0x00, 0xc6, 0xf0),
|
right_color: config.colors[1],
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
Self {
|
Self {
|
||||||
sim_id,
|
sim_id,
|
||||||
|
config,
|
||||||
memory: memory.clone(),
|
memory: memory.clone(),
|
||||||
worlds: memory.watch(sim_id, 0x0003d800, 0x400),
|
worlds: memory.watch(sim_id, 0x0003d800, 0x400),
|
||||||
bgmaps: memory.watch(sim_id, 0x00020000, 0x20000),
|
bgmaps: memory.watch(sim_id, 0x00020000, 0x20000),
|
||||||
@@ -467,12 +477,7 @@ impl WorldWindow {
|
|||||||
|
|
||||||
let left_color = self.params.left_color;
|
let left_color = self.params.left_color;
|
||||||
let right_color = self.params.right_color;
|
let right_color = self.params.right_color;
|
||||||
let both_color = Color32::from_rgb(
|
let both_color = left_color + right_color;
|
||||||
left_color.r() + right_color.r(),
|
|
||||||
left_color.g() + right_color.g(),
|
|
||||||
left_color.b() + right_color.b(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let painter = ui.painter();
|
let painter = ui.painter();
|
||||||
let draw_rect = |x1: f32, x2: f32, color: Color32| {
|
let draw_rect = |x1: f32, x2: f32, color: Color32| {
|
||||||
painter.line(
|
painter.line(
|
||||||
@@ -538,6 +543,12 @@ impl AppWindow for WorldWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn show(&mut self, ui: &mut Ui) {
|
fn show(&mut self, ui: &mut Ui) {
|
||||||
|
self.config.refresh();
|
||||||
|
self.params.write(WorldParams {
|
||||||
|
left_color: self.config.colors[0],
|
||||||
|
right_color: self.config.colors[1],
|
||||||
|
..*self.params
|
||||||
|
});
|
||||||
self.state.load(ui);
|
self.state.load(ui);
|
||||||
CentralPanel::default().show(ui, |ui| {
|
CentralPanel::default().show(ui, |ui| {
|
||||||
ui.horizontal_top(|ui| {
|
ui.horizontal_top(|ui| {
|
||||||
@@ -686,11 +697,7 @@ impl WorldRenderer {
|
|||||||
for (dst, shades) in image.pixels.iter_mut().zip(self.buffer.iter()) {
|
for (dst, shades) in image.pixels.iter_mut().zip(self.buffer.iter()) {
|
||||||
let left = colors[0][shades[0] as usize];
|
let left = colors[0][shades[0] as usize];
|
||||||
let right = colors[1][shades[1] as usize];
|
let right = colors[1][shades[1] as usize];
|
||||||
*dst = Color32::from_rgb(
|
*dst = left + right;
|
||||||
left.r() + right.r(),
|
|
||||||
left.g() + right.g(),
|
|
||||||
left.b() + right.b(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user