613 lines
19 KiB
Rust
613 lines
19 KiB
Rust
use crate::task::TaskId;
|
|
use crate::{CrashReport, GameLibrary, SystemOS};
|
|
use prometeu_hal::color::Color;
|
|
use prometeu_hal::primitives::Rect;
|
|
use prometeu_hal::{
|
|
FrameId, GfxUiCommand, InputPlatform, InputSignals, RenderSubmission, RuntimePlatform,
|
|
ShellUiFramePacket,
|
|
};
|
|
use std::path::PathBuf;
|
|
|
|
const SHELL_A_BUTTON: Rect = Rect { x: 112, y: 172, w: 112, h: 32 };
|
|
const SHELL_B_BUTTON: Rect = Rect { x: 256, y: 172, w: 112, h: 32 };
|
|
const CLOSE_BUTTON: Rect = Rect { x: 420, y: 24, w: 16, h: 16 };
|
|
const VIEWPORT: Rect = Rect { x: 0, y: 0, w: 480, h: 270 };
|
|
const HOME_PANEL: Rect = Rect { x: 48, y: 58, w: 384, h: 154 };
|
|
const TITLE_PANEL: Rect = Rect { x: 72, y: 34, w: 336, h: 28 };
|
|
const GAME_ROW_X: i32 = 72;
|
|
const GAME_ROW_Y: i32 = 90;
|
|
const GAME_ROW_W: i32 = 336;
|
|
const GAME_ROW_H: i32 = 22;
|
|
const GAME_ROW_GAP: i32 = 6;
|
|
const MAX_HOME_GAME_ROWS: usize = 3;
|
|
#[cfg(test)]
|
|
const SHELL_FRAME: Rect = Rect { x: 40, y: 20, w: 400, h: 220 };
|
|
|
|
const COLOR_BG: Color = Color::rgb(12, 14, 22);
|
|
const COLOR_PANEL: Color = Color::rgb(25, 31, 48);
|
|
const COLOR_PANEL_DARK: Color = Color::rgb(8, 10, 16);
|
|
const COLOR_BORDER: Color = Color::rgb(180, 218, 222);
|
|
const COLOR_HILITE: Color = Color::rgb(252, 226, 122);
|
|
const COLOR_TEXT: Color = Color::rgb(230, 236, 224);
|
|
const COLOR_MUTED: Color = Color::rgb(91, 112, 128);
|
|
const COLOR_BUTTON: Color = Color::rgb(45, 59, 82);
|
|
const COLOR_BUTTON_ACTIVE: Color = Color::rgb(72, 91, 122);
|
|
|
|
/// PrometeuHub: Launcher and system UI environment.
|
|
pub struct PrometeuHub {
|
|
home_input_armed: bool,
|
|
game_library: GameLibrary,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub enum NativeShellApp {
|
|
ShellA,
|
|
ShellB,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
struct HubButtonSpec {
|
|
app: NativeShellApp,
|
|
rect: Rect,
|
|
label: &'static str,
|
|
}
|
|
|
|
const HOME_BUTTONS: [HubButtonSpec; 2] = [
|
|
HubButtonSpec { app: NativeShellApp::ShellA, rect: SHELL_A_BUTTON, label: "ShellA" },
|
|
HubButtonSpec { app: NativeShellApp::ShellB, rect: SHELL_B_BUTTON, label: "ShellB" },
|
|
];
|
|
|
|
impl NativeShellApp {
|
|
pub fn app_id(self) -> u32 {
|
|
match self {
|
|
Self::ShellA => 10_001,
|
|
Self::ShellB => 10_002,
|
|
}
|
|
}
|
|
|
|
pub fn title(self) -> &'static str {
|
|
match self {
|
|
Self::ShellA => "ShellA",
|
|
Self::ShellB => "ShellB",
|
|
}
|
|
}
|
|
|
|
pub fn color(self) -> Color {
|
|
match self {
|
|
Self::ShellA => Color::GREEN,
|
|
Self::ShellB => Color::BLUE,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub enum SystemProfileAction {
|
|
LaunchNativeShell(NativeShellApp),
|
|
LaunchGame { path: PathBuf },
|
|
CloseShell,
|
|
}
|
|
|
|
pub struct SystemProfileUpdate {
|
|
pub crash: Option<CrashReport>,
|
|
pub action: Option<SystemProfileAction>,
|
|
}
|
|
|
|
impl Default for PrometeuHub {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
impl PrometeuHub {
|
|
pub fn new() -> Self {
|
|
Self { home_input_armed: false, game_library: GameLibrary::empty() }
|
|
}
|
|
|
|
pub fn init(&mut self) {
|
|
self.home_input_armed = false;
|
|
}
|
|
|
|
pub fn set_game_library(&mut self, game_library: GameLibrary) {
|
|
self.game_library = game_library;
|
|
}
|
|
|
|
pub fn game_library(&self) -> &GameLibrary {
|
|
&self.game_library
|
|
}
|
|
|
|
pub fn gui_update(
|
|
&mut self,
|
|
os: &mut SystemOS,
|
|
platform: &mut dyn RuntimePlatform,
|
|
) -> Option<SystemProfileAction> {
|
|
let in_shell = os.windows().focused_window().is_some();
|
|
|
|
if !in_shell {
|
|
if !self.home_input_armed {
|
|
self.home_input_armed = !activation_input_down(platform.input());
|
|
return None;
|
|
}
|
|
} else {
|
|
self.home_input_armed = false;
|
|
}
|
|
|
|
let input = platform.input();
|
|
if let Some(action) = action_for_click(
|
|
in_shell,
|
|
input.touch().x(),
|
|
input.touch().y(),
|
|
input.touch().f().pressed,
|
|
self.game_library.entries(),
|
|
) {
|
|
return Some(action);
|
|
}
|
|
|
|
None
|
|
}
|
|
|
|
pub fn render(&mut self, os: &mut SystemOS, platform: &mut dyn RuntimePlatform) {
|
|
let pointer_x = platform.input().touch().x();
|
|
let pointer_y = platform.input().touch().y();
|
|
|
|
let packet = if os.windows().window_count() == 0 {
|
|
render_home_packet(pointer_x, pointer_y, self.game_library.entries())
|
|
} else {
|
|
let mut commands = Vec::new();
|
|
for window in os.windows().windows() {
|
|
append_shell_window_commands(
|
|
&mut commands,
|
|
window.viewport,
|
|
window.color,
|
|
&window.title,
|
|
pointer_x,
|
|
pointer_y,
|
|
);
|
|
}
|
|
ShellUiFramePacket::new(commands)
|
|
};
|
|
platform
|
|
.render_submission_sink()
|
|
.submit_render_submission(RenderSubmission::shell_ui(FrameId::ZERO, packet))
|
|
.expect("hub render submission should publish");
|
|
}
|
|
|
|
pub fn update_shell_profile(
|
|
&mut self,
|
|
os: &mut SystemOS,
|
|
platform: &mut dyn RuntimePlatform,
|
|
) -> SystemProfileUpdate {
|
|
let mut action = self.gui_update(os, platform);
|
|
|
|
if os.windows().focused_window().is_some() && platform.input().pad().start().down {
|
|
action = Some(SystemProfileAction::CloseShell);
|
|
}
|
|
|
|
self.render(os, platform);
|
|
|
|
SystemProfileUpdate { crash: None, action }
|
|
}
|
|
|
|
pub fn update_vm_shell_profile(
|
|
&mut self,
|
|
os: &mut SystemOS,
|
|
task_id: TaskId,
|
|
signals: &InputSignals,
|
|
platform: &mut dyn RuntimePlatform,
|
|
) -> SystemProfileUpdate {
|
|
let mut crash = None;
|
|
let mut action = self.gui_update(os, platform);
|
|
|
|
if os.windows().focused_window().is_some() {
|
|
if platform.input().pad().start().down {
|
|
action = Some(SystemProfileAction::CloseShell);
|
|
} else if action != Some(SystemProfileAction::CloseShell) {
|
|
crash = os.vm().tick_session(task_id, signals, platform);
|
|
}
|
|
}
|
|
|
|
self.render(os, platform);
|
|
|
|
SystemProfileUpdate { crash, action }
|
|
}
|
|
|
|
pub fn update_native_shell_profile(
|
|
&mut self,
|
|
os: &mut SystemOS,
|
|
platform: &mut dyn RuntimePlatform,
|
|
) -> SystemProfileUpdate {
|
|
let mut action = self.gui_update(os, platform);
|
|
|
|
if os.windows().focused_window().is_some() && platform.input().pad().start().down {
|
|
action = Some(SystemProfileAction::CloseShell);
|
|
}
|
|
|
|
self.render(os, platform);
|
|
|
|
SystemProfileUpdate { crash: None, action }
|
|
}
|
|
}
|
|
|
|
fn action_for_click(
|
|
in_shell: bool,
|
|
x: i32,
|
|
y: i32,
|
|
pressed: bool,
|
|
game_entries: &[crate::GameLibraryEntry],
|
|
) -> Option<SystemProfileAction> {
|
|
if !pressed {
|
|
return None;
|
|
}
|
|
|
|
if in_shell {
|
|
return rect_contains(CLOSE_BUTTON, x, y).then_some(SystemProfileAction::CloseShell);
|
|
}
|
|
|
|
if let Some(entry) = game_entries
|
|
.iter()
|
|
.take(MAX_HOME_GAME_ROWS)
|
|
.enumerate()
|
|
.find(|(index, _entry)| rect_contains(game_row_rect(*index), x, y))
|
|
.map(|(_index, entry)| entry)
|
|
{
|
|
return Some(SystemProfileAction::LaunchGame { path: entry.path.clone() });
|
|
}
|
|
|
|
HOME_BUTTONS
|
|
.iter()
|
|
.find(|button| rect_contains(button.rect, x, y))
|
|
.map(|button| SystemProfileAction::LaunchNativeShell(button.app))
|
|
}
|
|
|
|
fn game_row_rect(index: usize) -> Rect {
|
|
Rect {
|
|
x: GAME_ROW_X,
|
|
y: GAME_ROW_Y + (GAME_ROW_H + GAME_ROW_GAP) * index as i32,
|
|
w: GAME_ROW_W,
|
|
h: GAME_ROW_H,
|
|
}
|
|
}
|
|
|
|
fn rect_contains(rect: Rect, x: i32, y: i32) -> bool {
|
|
x >= rect.x && x < rect.x + rect.w && y >= rect.y && y < rect.y + rect.h
|
|
}
|
|
|
|
fn activation_input_down(input: &dyn InputPlatform) -> bool {
|
|
input.touch().f().down
|
|
|| input.pad().a().down
|
|
|| input.pad().b().down
|
|
|| input.pad().start().down
|
|
}
|
|
|
|
fn render_home_packet(
|
|
pointer_x: i32,
|
|
pointer_y: i32,
|
|
game_entries: &[crate::GameLibraryEntry],
|
|
) -> ShellUiFramePacket {
|
|
let mut commands = Vec::new();
|
|
commands.push(GfxUiCommand::Clear { color: COLOR_BG });
|
|
fill_rect(&mut commands, VIEWPORT, COLOR_BG);
|
|
draw_panel(&mut commands, TITLE_PANEL, COLOR_PANEL_DARK, COLOR_BORDER);
|
|
draw_buffer_text(
|
|
&mut commands,
|
|
TITLE_PANEL.x + 58,
|
|
TITLE_PANEL.y + 9,
|
|
"PROMETEU HUB",
|
|
COLOR_TEXT,
|
|
);
|
|
|
|
draw_panel(&mut commands, HOME_PANEL, COLOR_PANEL, COLOR_BORDER);
|
|
draw_buffer_text(&mut commands, HOME_PANEL.x + 26, HOME_PANEL.y + 22, "Games", COLOR_TEXT);
|
|
draw_buffer_text(
|
|
&mut commands,
|
|
HOME_PANEL.x + 26,
|
|
HOME_PANEL.y + 40,
|
|
"local library",
|
|
COLOR_MUTED,
|
|
);
|
|
|
|
if game_entries.is_empty() {
|
|
draw_buffer_text(&mut commands, GAME_ROW_X, GAME_ROW_Y + 6, "No games found", COLOR_MUTED);
|
|
} else {
|
|
for (index, entry) in game_entries.iter().take(MAX_HOME_GAME_ROWS).enumerate() {
|
|
let rect = game_row_rect(index);
|
|
let hovered = rect_contains(rect, pointer_x, pointer_y);
|
|
let text = format!("{} #{} {}", entry.title, entry.app_id, entry.app_version);
|
|
draw_game_row(&mut commands, rect, &text, hovered);
|
|
}
|
|
}
|
|
|
|
for button in HOME_BUTTONS {
|
|
let hovered = rect_contains(button.rect, pointer_x, pointer_y);
|
|
draw_button(&mut commands, button.rect, button.label, hovered);
|
|
}
|
|
|
|
ShellUiFramePacket::new(commands)
|
|
}
|
|
|
|
fn append_shell_window_commands(
|
|
commands: &mut Vec<GfxUiCommand>,
|
|
viewport: Rect,
|
|
color: Color,
|
|
title: &str,
|
|
pointer_x: i32,
|
|
pointer_y: i32,
|
|
) {
|
|
fill_rect(commands, VIEWPORT, COLOR_BG);
|
|
draw_panel(commands, viewport, COLOR_PANEL_DARK, COLOR_BORDER);
|
|
fill_rect(
|
|
commands,
|
|
Rect { x: viewport.x + 4, y: viewport.y + 24, w: viewport.w - 8, h: viewport.h - 28 },
|
|
color,
|
|
);
|
|
draw_buffer_text(commands, viewport.x + 10, viewport.y + 8, title, COLOR_TEXT);
|
|
draw_buffer_text(
|
|
commands,
|
|
viewport.x + 18,
|
|
viewport.y + 44,
|
|
"native fake shell",
|
|
COLOR_PANEL_DARK,
|
|
);
|
|
|
|
let close_hovered = rect_contains(CLOSE_BUTTON, pointer_x, pointer_y);
|
|
let close_fill = if close_hovered { COLOR_HILITE } else { COLOR_BUTTON };
|
|
fill_rect(commands, CLOSE_BUTTON, close_fill);
|
|
draw_rect(commands, CLOSE_BUTTON, COLOR_BORDER);
|
|
draw_buffer_text(commands, CLOSE_BUTTON.x + 5, CLOSE_BUTTON.y + 4, "x", COLOR_TEXT);
|
|
}
|
|
|
|
fn fill_rect(commands: &mut Vec<GfxUiCommand>, rect: Rect, color: Color) {
|
|
commands.push(GfxUiCommand::FillRect { rect, color });
|
|
}
|
|
|
|
fn draw_rect(commands: &mut Vec<GfxUiCommand>, rect: Rect, color: Color) {
|
|
commands.push(GfxUiCommand::DrawLine {
|
|
x0: rect.x,
|
|
y0: rect.y,
|
|
x1: rect.x + rect.w - 1,
|
|
y1: rect.y,
|
|
color,
|
|
});
|
|
commands.push(GfxUiCommand::DrawLine {
|
|
x0: rect.x,
|
|
y0: rect.y + rect.h - 1,
|
|
x1: rect.x + rect.w - 1,
|
|
y1: rect.y + rect.h - 1,
|
|
color,
|
|
});
|
|
commands.push(GfxUiCommand::DrawLine {
|
|
x0: rect.x,
|
|
y0: rect.y,
|
|
x1: rect.x,
|
|
y1: rect.y + rect.h - 1,
|
|
color,
|
|
});
|
|
commands.push(GfxUiCommand::DrawLine {
|
|
x0: rect.x + rect.w - 1,
|
|
y0: rect.y,
|
|
x1: rect.x + rect.w - 1,
|
|
y1: rect.y + rect.h - 1,
|
|
color,
|
|
});
|
|
}
|
|
|
|
fn draw_panel(commands: &mut Vec<GfxUiCommand>, rect: Rect, fill: Color, border: Color) {
|
|
fill_rect(commands, rect, fill);
|
|
draw_rect(commands, rect, border);
|
|
draw_rect(
|
|
commands,
|
|
Rect { x: rect.x + 2, y: rect.y + 2, w: rect.w - 4, h: rect.h - 4 },
|
|
COLOR_MUTED,
|
|
);
|
|
}
|
|
|
|
fn draw_button(commands: &mut Vec<GfxUiCommand>, rect: Rect, label: &str, hovered: bool) {
|
|
let fill = if hovered { COLOR_BUTTON_ACTIVE } else { COLOR_BUTTON };
|
|
let border = if hovered { COLOR_HILITE } else { COLOR_BORDER };
|
|
fill_rect(commands, rect, fill);
|
|
draw_rect(commands, rect, border);
|
|
draw_rect(
|
|
commands,
|
|
Rect { x: rect.x + 3, y: rect.y + 3, w: rect.w - 6, h: rect.h - 6 },
|
|
COLOR_MUTED,
|
|
);
|
|
fill_rect(commands, Rect { x: rect.x + 12, y: rect.y + 8, w: 14, h: 16 }, border);
|
|
draw_rect(commands, Rect { x: rect.x + 34, y: rect.y + 8, w: 62, h: 16 }, COLOR_PANEL_DARK);
|
|
draw_buffer_text(commands, rect.x + 40, rect.y + 13, label, COLOR_TEXT);
|
|
}
|
|
|
|
fn draw_game_row(commands: &mut Vec<GfxUiCommand>, rect: Rect, label: &str, hovered: bool) {
|
|
let fill = if hovered { COLOR_BUTTON_ACTIVE } else { COLOR_PANEL_DARK };
|
|
let border = if hovered { COLOR_HILITE } else { COLOR_MUTED };
|
|
fill_rect(commands, rect, fill);
|
|
draw_rect(commands, rect, border);
|
|
draw_buffer_text(commands, rect.x + 8, rect.y + 7, label, COLOR_TEXT);
|
|
}
|
|
|
|
fn draw_buffer_text(commands: &mut Vec<GfxUiCommand>, x: i32, y: i32, text: &str, color: Color) {
|
|
commands.push(GfxUiCommand::DrawText { x, y, text: text.to_string(), color });
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use crate::task::TaskId;
|
|
use crate::windows::WindowOwner;
|
|
use prometeu_drivers::TestPlatform;
|
|
use prometeu_hal::{InputSignals, RuntimePlatform};
|
|
|
|
fn focused_native_shell_fixture(
|
|
signals: &InputSignals,
|
|
) -> (PrometeuHub, SystemOS, TestPlatform) {
|
|
let hub = PrometeuHub::new();
|
|
let mut os = SystemOS::new(None);
|
|
let mut platform = TestPlatform::new();
|
|
|
|
{
|
|
let mut windows = os.windows();
|
|
let id = windows.add_window(
|
|
"ShellA".to_string(),
|
|
WindowOwner::Task(TaskId(7)),
|
|
SHELL_FRAME,
|
|
Color::GREEN,
|
|
);
|
|
windows.set_focus(id);
|
|
}
|
|
|
|
platform.input_mut().pad_mut().begin_frame(signals);
|
|
platform.input_mut().touch_mut().begin_frame(signals);
|
|
|
|
(hub, os, platform)
|
|
}
|
|
|
|
#[test]
|
|
fn shell_a_button_click_emits_launch_action() {
|
|
assert_eq!(
|
|
action_for_click(false, HOME_BUTTONS[0].rect.x, HOME_BUTTONS[0].rect.y, true, &[]),
|
|
Some(SystemProfileAction::LaunchNativeShell(NativeShellApp::ShellA))
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn shell_b_button_click_emits_launch_action() {
|
|
assert_eq!(
|
|
action_for_click(
|
|
false,
|
|
SHELL_B_BUTTON.x + SHELL_B_BUTTON.w - 1,
|
|
SHELL_B_BUTTON.y,
|
|
true,
|
|
&[]
|
|
),
|
|
Some(SystemProfileAction::LaunchNativeShell(NativeShellApp::ShellB))
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn button_hit_test_excludes_right_and_bottom_edges() {
|
|
assert_eq!(
|
|
action_for_click(
|
|
false,
|
|
SHELL_A_BUTTON.x + SHELL_A_BUTTON.w,
|
|
SHELL_A_BUTTON.y,
|
|
true,
|
|
&[]
|
|
),
|
|
None
|
|
);
|
|
assert_eq!(
|
|
action_for_click(
|
|
false,
|
|
SHELL_A_BUTTON.x,
|
|
SHELL_A_BUTTON.y + SHELL_A_BUTTON.h,
|
|
true,
|
|
&[]
|
|
),
|
|
None
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn held_pointer_does_not_emit_click_action() {
|
|
assert_eq!(action_for_click(false, SHELL_A_BUTTON.x, SHELL_A_BUTTON.y, false, &[]), None);
|
|
}
|
|
|
|
#[test]
|
|
fn hub_starts_with_home_input_disarmed() {
|
|
let hub = PrometeuHub::new();
|
|
assert!(!hub.home_input_armed);
|
|
}
|
|
|
|
#[test]
|
|
fn game_row_click_emits_launch_game_action() {
|
|
let entry = crate::GameLibraryEntry {
|
|
manifest: prometeu_hal::cartridge::CartridgeManifest {
|
|
magic: "PMTU".to_string(),
|
|
cartridge_version: 1,
|
|
app_id: 42,
|
|
title: "Stress".to_string(),
|
|
app_version: "1.0.0".to_string(),
|
|
app_mode: prometeu_hal::app_mode::AppMode::Game,
|
|
capabilities: vec![],
|
|
},
|
|
title: "Stress".to_string(),
|
|
app_id: 42,
|
|
app_version: "1.0.0".to_string(),
|
|
path: PathBuf::from("test-cartridges/stress-console"),
|
|
discovered_at: std::time::SystemTime::UNIX_EPOCH,
|
|
};
|
|
|
|
assert_eq!(
|
|
action_for_click(false, GAME_ROW_X, GAME_ROW_Y, true, std::slice::from_ref(&entry)),
|
|
Some(SystemProfileAction::LaunchGame { path: entry.path.clone() })
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn close_button_click_emits_close_only_in_shell() {
|
|
assert_eq!(
|
|
action_for_click(true, CLOSE_BUTTON.x, CLOSE_BUTTON.y, true, &[]),
|
|
Some(SystemProfileAction::CloseShell)
|
|
);
|
|
assert_eq!(action_for_click(false, CLOSE_BUTTON.x, CLOSE_BUTTON.y, true, &[]), None);
|
|
}
|
|
|
|
#[test]
|
|
fn home_layout_exposes_exactly_two_manual_shell_buttons() {
|
|
assert_eq!(HOME_BUTTONS.len(), 2);
|
|
assert_eq!(HOME_BUTTONS[0].label, "ShellA");
|
|
assert_eq!(HOME_BUTTONS[0].app, NativeShellApp::ShellA);
|
|
assert_eq!(HOME_BUTTONS[1].label, "ShellB");
|
|
assert_eq!(HOME_BUTTONS[1].app, NativeShellApp::ShellB);
|
|
}
|
|
|
|
#[test]
|
|
fn home_render_produces_shell_ui_packet_commands() {
|
|
let packet = render_home_packet(SHELL_A_BUTTON.x, SHELL_A_BUTTON.y, &[]);
|
|
|
|
assert!(matches!(packet.commands.first(), Some(GfxUiCommand::Clear { .. })));
|
|
assert!(packet.commands.iter().any(|command| {
|
|
matches!(command, GfxUiCommand::DrawText { text, .. } if text == "PROMETEU HUB")
|
|
}));
|
|
}
|
|
|
|
#[test]
|
|
fn shell_close_button_sits_inside_shell_frame() {
|
|
assert!(rect_contains(SHELL_FRAME, CLOSE_BUTTON.x, CLOSE_BUTTON.y));
|
|
assert!(rect_contains(
|
|
SHELL_FRAME,
|
|
CLOSE_BUTTON.x + CLOSE_BUTTON.w - 1,
|
|
CLOSE_BUTTON.y + CLOSE_BUTTON.h - 1
|
|
));
|
|
}
|
|
|
|
#[test]
|
|
fn native_shell_profile_start_close_does_not_tick_vm() {
|
|
let signals = InputSignals { start_signal: true, ..Default::default() };
|
|
let (mut hub, mut os, mut platform) = focused_native_shell_fixture(&signals);
|
|
|
|
let tick_index_before_update = os.vm().tick_index();
|
|
let outcome = hub.update_native_shell_profile(&mut os, &mut platform);
|
|
|
|
assert_eq!(outcome.action, Some(SystemProfileAction::CloseShell));
|
|
assert!(outcome.crash.is_none());
|
|
assert_eq!(os.vm().tick_index(), tick_index_before_update);
|
|
}
|
|
|
|
#[test]
|
|
fn native_shell_profile_close_button_does_not_tick_vm() {
|
|
let signals = InputSignals {
|
|
f_signal: true,
|
|
x_pos: CLOSE_BUTTON.x,
|
|
y_pos: CLOSE_BUTTON.y,
|
|
..Default::default()
|
|
};
|
|
let (mut hub, mut os, mut platform) = focused_native_shell_fixture(&signals);
|
|
|
|
let tick_index_before_update = os.vm().tick_index();
|
|
let outcome = hub.update_native_shell_profile(&mut os, &mut platform);
|
|
|
|
assert_eq!(outcome.action, Some(SystemProfileAction::CloseShell));
|
|
assert!(outcome.crash.is_none());
|
|
assert_eq!(os.vm().tick_index(), tick_index_before_update);
|
|
}
|
|
}
|