feat: add maps layouts
This commit is contained in:
@ -58,8 +58,10 @@ pub struct Config {
|
||||
/// Saved position of the layout window (logical pixels).
|
||||
pub layout_x: i32,
|
||||
pub layout_y: i32,
|
||||
/// Size (px) of the main layout image rendered in the viewer.
|
||||
pub layout_size: u32,
|
||||
/// Saved size of the layout window (logical pixels). The image scales to fill
|
||||
/// it; the user adjusts it via the corner resize grip or the settings fields.
|
||||
pub layout_w: u32,
|
||||
pub layout_h: u32,
|
||||
|
||||
// --- campaign timer ---
|
||||
pub timer_enabled: bool,
|
||||
@ -107,7 +109,8 @@ impl Default for Config {
|
||||
feature_layouts: true,
|
||||
layout_x: 600,
|
||||
layout_y: 160,
|
||||
layout_size: 360,
|
||||
layout_w: 480,
|
||||
layout_h: 600,
|
||||
timer_enabled: true,
|
||||
timer_pause_in_town: true,
|
||||
timer_afk_enabled: true,
|
||||
|
||||
@ -39,6 +39,10 @@ pub struct AppState {
|
||||
// the offset from the window's top-left to the cursor; a background thread
|
||||
// follows the mouse via xdotool (Tauri/KWin won't reposition this window).
|
||||
pub layout_drag: Mutex<Option<(i32, i32)>>,
|
||||
// While the user drags the layout window's corner grip: Some(()) signals a
|
||||
// background thread to follow the mouse and resize the window via set_size
|
||||
// (KWin gives this decorationless window no resize borders of its own).
|
||||
pub layout_resize: Mutex<Option<()>>,
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@ -284,12 +288,80 @@ fn toggle_layout(app: AppHandle, state: tauri::State<AppState>) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Resize the layout window to fit its rendered content (called by the frontend
|
||||
/// whenever the viewer's content size changes).
|
||||
/// Resize the layout window to an explicit size and persist it (called by the
|
||||
/// settings fields). The grip uses `start_layout_resize` for live dragging.
|
||||
#[tauri::command]
|
||||
fn set_layout_size(app: AppHandle, width: u32, height: u32) {
|
||||
fn set_layout_size(app: AppHandle, width: u32, height: u32, state: tauri::State<AppState>) {
|
||||
let (w_px, h_px) = (width.max(MIN_LAYOUT_W), height.max(MIN_LAYOUT_H));
|
||||
if let Some(w) = app.get_webview_window("layout") {
|
||||
let _ = w.set_size(LogicalSize::new(width.max(80), height.max(80)));
|
||||
let _ = w.set_size(LogicalSize::new(w_px, h_px));
|
||||
}
|
||||
let mut cfg = state.config.lock().unwrap();
|
||||
cfg.layout_w = w_px;
|
||||
cfg.layout_h = h_px;
|
||||
cfg.save();
|
||||
}
|
||||
|
||||
/// Minimum layout-window size (logical px), shared by the grip and settings.
|
||||
const MIN_LAYOUT_W: u32 = 240;
|
||||
const MIN_LAYOUT_H: u32 = 200;
|
||||
|
||||
/// Begin resizing the layout window by its corner grip. Mirrors the drag
|
||||
/// machinery: a background thread follows the mouse (pointermove is unreliable
|
||||
/// in this WebKitGTK window) and resizes via `set_size` until `end_layout_resize`.
|
||||
#[tauri::command]
|
||||
fn start_layout_resize(app: AppHandle, state: tauri::State<AppState>) {
|
||||
let Some(w) = app.get_webview_window("layout") else {
|
||||
return;
|
||||
};
|
||||
let Some((mx, my)) = crate::poe::mouse_position() else {
|
||||
return;
|
||||
};
|
||||
let scale = w.scale_factor().unwrap_or(1.0);
|
||||
let Ok(base) = w.inner_size() else {
|
||||
return;
|
||||
};
|
||||
let base = base.to_logical::<i32>(scale);
|
||||
*state.layout_resize.lock().unwrap() = Some(());
|
||||
|
||||
let app = app.clone();
|
||||
std::thread::spawn(move || {
|
||||
let start = std::time::Instant::now();
|
||||
loop {
|
||||
if app.state::<AppState>().layout_resize.lock().unwrap().is_none() {
|
||||
break;
|
||||
}
|
||||
// Safety net so a missed pointerup can't pin the size to the cursor.
|
||||
if start.elapsed().as_secs() > 30 {
|
||||
*app.state::<AppState>().layout_resize.lock().unwrap() = None;
|
||||
break;
|
||||
}
|
||||
if let (Some(w), Some((cx, cy))) =
|
||||
(app.get_webview_window("layout"), crate::poe::mouse_position())
|
||||
{
|
||||
let nw = (base.width + (cx - mx)).max(MIN_LAYOUT_W as i32) as u32;
|
||||
let nh = (base.height + (cy - my)).max(MIN_LAYOUT_H as i32) as u32;
|
||||
let _ = w.set_size(LogicalSize::new(nw, nh));
|
||||
}
|
||||
std::thread::sleep(std::time::Duration::from_millis(16));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Stop resizing the layout window and persist its final size.
|
||||
#[tauri::command]
|
||||
fn end_layout_resize(app: AppHandle, state: tauri::State<AppState>) {
|
||||
*state.layout_resize.lock().unwrap() = None;
|
||||
std::thread::sleep(std::time::Duration::from_millis(30));
|
||||
if let Some(w) = app.get_webview_window("layout") {
|
||||
let scale = w.scale_factor().unwrap_or(1.0);
|
||||
if let Ok(s) = w.inner_size() {
|
||||
let ls = s.to_logical::<u32>(scale);
|
||||
let mut cfg = state.config.lock().unwrap();
|
||||
cfg.layout_w = ls.width.max(MIN_LAYOUT_W);
|
||||
cfg.layout_h = ls.height.max(MIN_LAYOUT_H);
|
||||
cfg.save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -625,6 +697,7 @@ pub fn run() {
|
||||
status: Mutex::new(Status::default()),
|
||||
timer: Mutex::new(live_timer),
|
||||
layout_drag: Mutex::new(None),
|
||||
layout_resize: Mutex::new(None),
|
||||
};
|
||||
|
||||
tauri::Builder::default()
|
||||
@ -675,6 +748,8 @@ pub fn run() {
|
||||
save_layout_geometry,
|
||||
start_layout_drag,
|
||||
end_layout_drag,
|
||||
start_layout_resize,
|
||||
end_layout_resize,
|
||||
])
|
||||
.setup(|app| {
|
||||
let handle = app.handle().clone();
|
||||
@ -736,10 +811,10 @@ pub fn run() {
|
||||
// Unlike the overlay it is NOT transparent: it's an opaque panel, and
|
||||
// transparent WebKitGTK windows don't reliably repaint here (the view
|
||||
// stays blank/ghosted), whereas an opaque window composites fine.
|
||||
let (lx, ly, lsz) = {
|
||||
let (lx, ly, lw, lh) = {
|
||||
let state = handle.state::<AppState>();
|
||||
let cfg = state.config.lock().unwrap();
|
||||
(cfg.layout_x, cfg.layout_y, cfg.layout_size)
|
||||
(cfg.layout_x, cfg.layout_y, cfg.layout_w, cfg.layout_h)
|
||||
};
|
||||
let _ = WebviewWindowBuilder::new(
|
||||
&handle,
|
||||
@ -747,7 +822,8 @@ pub fn run() {
|
||||
WebviewUrl::App("index.html".into()),
|
||||
)
|
||||
.title("Exile UI Layouts")
|
||||
.inner_size(lsz as f64 + 24.0, lsz as f64 + 110.0)
|
||||
.inner_size(lw as f64, lh as f64)
|
||||
.min_inner_size(MIN_LAYOUT_W as f64, MIN_LAYOUT_H as f64)
|
||||
.position(lx as f64, ly as f64)
|
||||
.decorations(false)
|
||||
.always_on_top(true)
|
||||
|
||||
Reference in New Issue
Block a user