fixed issues with Input API

This commit is contained in:
bQUARKz 2026-02-08 13:52:51 +00:00
parent 0a087d51fb
commit 2592b1a1d1
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
17 changed files with 457 additions and 90 deletions

4
Cargo.lock generated
View File

@ -1940,13 +1940,9 @@ name = "prometeu-hardware"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"prometeu-abi", "prometeu-abi",
"prometeu-bytecode",
"prometeu-hardware-contract", "prometeu-hardware-contract",
"prometeu-kernel",
"prometeu-vm", "prometeu-vm",
"serde",
"serde_json", "serde_json",
"url",
] ]
[[package]] [[package]]

View File

@ -15,12 +15,13 @@ pub enum ButtonId {
Select = 11, Select = 11,
} }
#[repr(C)]
#[derive(Default, Clone, Copy, Debug)] #[derive(Default, Clone, Copy, Debug)]
pub struct Button { pub struct Button {
pub pressed: bool, pub pressed: bool,
pub released: bool, pub released: bool,
pub down: bool, pub down: bool,
pub hold_frames: u32, pub hold_frames: u16,
} }
impl Button { impl Button {

View File

@ -66,6 +66,23 @@ pub enum Syscall {
TouchIsReleased = 0x2105, TouchIsReleased = 0x2105,
/// Returns how many frames the pointer has been held down. /// Returns how many frames the pointer has been held down.
TouchGetHold = 0x2106, TouchGetHold = 0x2106,
/// Returns the full Button struct (6 bytes) for the touch finger state.
TouchGetFinger = 0x2107,
// --- Input (Pad service-based) ---
/// Returns the full Button struct (6 bytes) for each gamepad button
PadGetUp = 0x2200,
PadGetDown = 0x2201,
PadGetLeft = 0x2202,
PadGetRight = 0x2203,
PadGetA = 0x2204,
PadGetB = 0x2205,
PadGetX = 0x2206,
PadGetY = 0x2207,
PadGetL = 0x2208,
PadGetR = 0x2209,
PadGetStart = 0x220A,
PadGetSelect = 0x220B,
// --- Audio --- // --- Audio ---
/// Starts playback of a sound sample by its Bank and ID. /// Starts playback of a sound sample by its Bank and ID.
@ -138,6 +155,19 @@ impl Syscall {
0x2104 => Some(Self::TouchIsPressed), 0x2104 => Some(Self::TouchIsPressed),
0x2105 => Some(Self::TouchIsReleased), 0x2105 => Some(Self::TouchIsReleased),
0x2106 => Some(Self::TouchGetHold), 0x2106 => Some(Self::TouchGetHold),
0x2107 => Some(Self::TouchGetFinger),
0x2200 => Some(Self::PadGetUp),
0x2201 => Some(Self::PadGetDown),
0x2202 => Some(Self::PadGetLeft),
0x2203 => Some(Self::PadGetRight),
0x2204 => Some(Self::PadGetA),
0x2205 => Some(Self::PadGetB),
0x2206 => Some(Self::PadGetX),
0x2207 => Some(Self::PadGetY),
0x2208 => Some(Self::PadGetL),
0x2209 => Some(Self::PadGetR),
0x220A => Some(Self::PadGetStart),
0x220B => Some(Self::PadGetSelect),
0x3001 => Some(Self::AudioPlaySample), 0x3001 => Some(Self::AudioPlaySample),
0x3002 => Some(Self::AudioPlay), 0x3002 => Some(Self::AudioPlay),
0x4001 => Some(Self::FsOpen), 0x4001 => Some(Self::FsOpen),
@ -184,6 +214,19 @@ impl Syscall {
Self::TouchIsPressed => 0, Self::TouchIsPressed => 0,
Self::TouchIsReleased => 0, Self::TouchIsReleased => 0,
Self::TouchGetHold => 0, Self::TouchGetHold => 0,
Self::TouchGetFinger => 0,
Self::PadGetUp => 0,
Self::PadGetDown => 0,
Self::PadGetLeft => 0,
Self::PadGetRight => 0,
Self::PadGetA => 0,
Self::PadGetB => 0,
Self::PadGetX => 0,
Self::PadGetY => 0,
Self::PadGetL => 0,
Self::PadGetR => 0,
Self::PadGetStart => 0,
Self::PadGetSelect => 0,
Self::AudioPlaySample => 5, Self::AudioPlaySample => 5,
Self::AudioPlay => 7, Self::AudioPlay => 7,
Self::FsOpen => 1, Self::FsOpen => 1,
@ -209,6 +252,20 @@ impl Syscall {
Self::GfxClear565 => 0, Self::GfxClear565 => 0,
Self::InputPadSnapshot => 48, Self::InputPadSnapshot => 48,
Self::InputTouchSnapshot => 6, Self::InputTouchSnapshot => 6,
// Touch finger and Pad per-button services return a Button (4 slots)
Self::TouchGetFinger => 4,
Self::PadGetUp
| Self::PadGetDown
| Self::PadGetLeft
| Self::PadGetRight
| Self::PadGetA
| Self::PadGetB
| Self::PadGetX
| Self::PadGetY
| Self::PadGetL
| Self::PadGetR
| Self::PadGetStart
| Self::PadGetSelect => 4,
_ => 1, _ => 1,
} }
} }
@ -238,6 +295,19 @@ impl Syscall {
Self::TouchIsPressed => "TouchIsPressed", Self::TouchIsPressed => "TouchIsPressed",
Self::TouchIsReleased => "TouchIsReleased", Self::TouchIsReleased => "TouchIsReleased",
Self::TouchGetHold => "TouchGetHold", Self::TouchGetHold => "TouchGetHold",
Self::TouchGetFinger => "TouchGetFinger",
Self::PadGetUp => "PadGetUp",
Self::PadGetDown => "PadGetDown",
Self::PadGetLeft => "PadGetLeft",
Self::PadGetRight => "PadGetRight",
Self::PadGetA => "PadGetA",
Self::PadGetB => "PadGetB",
Self::PadGetX => "PadGetX",
Self::PadGetY => "PadGetY",
Self::PadGetL => "PadGetL",
Self::PadGetR => "PadGetR",
Self::PadGetStart => "PadGetStart",
Self::PadGetSelect => "PadGetSelect",
Self::AudioPlaySample => "AudioPlaySample", Self::AudioPlaySample => "AudioPlaySample",
Self::AudioPlay => "AudioPlay", Self::AudioPlay => "AudioPlay",
Self::FsOpen => "FsOpen", Self::FsOpen => "FsOpen",

View File

@ -59,7 +59,7 @@ impl ContractRegistry {
}); });
mappings.insert("Gfx".to_string(), gfx); mappings.insert("Gfx".to_string(), gfx);
// Input mappings // Input legacy mappings (kept for backward compatibility)
let mut input = HashMap::new(); let mut input = HashMap::new();
input.insert("pad".to_string(), ContractMethod { input.insert("pad".to_string(), ContractMethod {
id: 0x2010, id: 0x2010,
@ -73,37 +73,90 @@ impl ContractRegistry {
}); });
mappings.insert("Input".to_string(), input); mappings.insert("Input".to_string(), input);
// Touch mappings // New Pad service-based mappings (each button as a method returning Button)
let mut pad = HashMap::new();
// NOTE: The syscalls for per-button access must be handled by the runtime.
// We reserve the 0x2200..0x220B range for Pad buttons returning a Button struct (6 bytes).
pad.insert("up".to_string(), ContractMethod {
id: 0x2200,
params: vec![],
return_type: PbsType::Struct("Button".to_string()),
});
pad.insert("down".to_string(), ContractMethod {
id: 0x2201,
params: vec![],
return_type: PbsType::Struct("Button".to_string()),
});
pad.insert("left".to_string(), ContractMethod {
id: 0x2202,
params: vec![],
return_type: PbsType::Struct("Button".to_string()),
});
pad.insert("right".to_string(), ContractMethod {
id: 0x2203,
params: vec![],
return_type: PbsType::Struct("Button".to_string()),
});
pad.insert("a".to_string(), ContractMethod {
id: 0x2204,
params: vec![],
return_type: PbsType::Struct("Button".to_string()),
});
pad.insert("b".to_string(), ContractMethod {
id: 0x2205,
params: vec![],
return_type: PbsType::Struct("Button".to_string()),
});
pad.insert("x".to_string(), ContractMethod {
id: 0x2206,
params: vec![],
return_type: PbsType::Struct("Button".to_string()),
});
pad.insert("y".to_string(), ContractMethod {
id: 0x2207,
params: vec![],
return_type: PbsType::Struct("Button".to_string()),
});
pad.insert("l".to_string(), ContractMethod {
id: 0x2208,
params: vec![],
return_type: PbsType::Struct("Button".to_string()),
});
pad.insert("r".to_string(), ContractMethod {
id: 0x2209,
params: vec![],
return_type: PbsType::Struct("Button".to_string()),
});
pad.insert("start".to_string(), ContractMethod {
id: 0x220A,
params: vec![],
return_type: PbsType::Struct("Button".to_string()),
});
pad.insert("select".to_string(), ContractMethod {
id: 0x220B,
params: vec![],
return_type: PbsType::Struct("Button".to_string()),
});
mappings.insert("Pad".to_string(), pad);
// Touch mappings (service-based)
let mut touch = HashMap::new(); let mut touch = HashMap::new();
touch.insert("getX".to_string(), ContractMethod { // SDK agora expõe screen_x/screen_y/finger()
touch.insert("screen_x".to_string(), ContractMethod {
id: 0x2101, id: 0x2101,
params: vec![], params: vec![],
return_type: PbsType::Int, return_type: PbsType::Int,
}); });
touch.insert("getY".to_string(), ContractMethod { touch.insert("screen_y".to_string(), ContractMethod {
id: 0x2102, id: 0x2102,
params: vec![], params: vec![],
return_type: PbsType::Int, return_type: PbsType::Int,
}); });
touch.insert("isDown".to_string(), ContractMethod { // Novo syscall dedicado para retornar Button completo do touch (dedo)
id: 0x2103, touch.insert("finger".to_string(), ContractMethod {
id: 0x2107,
params: vec![], params: vec![],
return_type: PbsType::Bool, return_type: PbsType::Struct("Button".to_string()),
});
touch.insert("isPressed".to_string(), ContractMethod {
id: 0x2104,
params: vec![],
return_type: PbsType::Bool,
});
touch.insert("isReleased".to_string(), ContractMethod {
id: 0x2105,
params: vec![],
return_type: PbsType::Bool,
});
touch.insert("getHold".to_string(), ContractMethod {
id: 0x2106,
params: vec![],
return_type: PbsType::Int,
}); });
mappings.insert("Touch".to_string(), touch); mappings.insert("Touch".to_string(), touch);

View File

@ -57,6 +57,8 @@ impl<'a> Lowerer<'a> {
let mut struct_slots = HashMap::new(); let mut struct_slots = HashMap::new();
struct_slots.insert("Color".to_string(), 1); struct_slots.insert("Color".to_string(), 1);
struct_slots.insert("ButtonState".to_string(), 4); struct_slots.insert("ButtonState".to_string(), 4);
// New service-based input returns `Button` (same layout as legacy ButtonState: 4 slots)
struct_slots.insert("Button".to_string(), 4);
struct_slots.insert("Pad".to_string(), 48); struct_slots.insert("Pad".to_string(), 48);
struct_slots.insert("Touch".to_string(), 6); struct_slots.insert("Touch".to_string(), 6);
@ -564,7 +566,46 @@ impl<'a> Lowerer<'a> {
fn lower_block(&mut self, _node: NodeId, n: &BlockNodeArena) -> Result<(), ()> { fn lower_block(&mut self, _node: NodeId, n: &BlockNodeArena) -> Result<(), ()> {
self.local_vars.push(HashMap::new()); self.local_vars.push(HashMap::new());
for stmt in &n.stmts { for stmt in &n.stmts {
// Lower the statement normally
self.lower_node(*stmt)?; self.lower_node(*stmt)?;
// Guardrail: if the statement is a standalone Call that returns values,
// discard them to keep the stack balanced. This is essential for host
// contract calls that return multi-slot structs (e.g., Button=4 slots).
if let NodeKind::Call(call) = self.arena.kind(*stmt) {
// Try to compute return slots conservatively
let mut return_slots: u32 = 0;
match self.arena.kind(call.callee) {
NodeKind::MemberAccess(ma) => {
// Static contract call: Contract.method(...)
if let NodeKind::Ident(obj_id) = self.arena.kind(ma.object) {
let contract_name = self.interner.resolve(obj_id.name);
let method_name = self.interner.resolve(ma.member);
if let Some(method) = self.contract_registry.get_method(contract_name, method_name) {
let ty = self.convert_pbs_type(&method.return_type);
return_slots = self.get_type_slots(&ty);
}
}
}
NodeKind::Ident(id_node) => {
// Calling a local function or imported symbol — attempt best effort via symbol table
let callee_name = self.interner.resolve(id_node.name).to_string();
if let Some(func_id) = self.function_ids.get(&callee_name) {
let _ = func_id; // unresolved return info in v0 → assume 0 (no discard)
} else {
// ImportCall or unknown — assume 0 (safe)
}
}
_ => {}
}
if return_slots > 0 {
for _ in 0..return_slots {
self.emit(InstrKind::Pop);
}
}
}
} }
if let Some(tail) = n.tail { if let Some(tail) = n.tail {
self.lower_node(tail)?; self.lower_node(tail)?;
@ -585,11 +626,17 @@ impl<'a> Lowerer<'a> {
if let NodeKind::Ident(obj) = self.arena.kind(ma.object) { if let NodeKind::Ident(obj) = self.arena.kind(ma.object) {
let obj_name = self.interner.resolve(obj.name); let obj_name = self.interner.resolve(obj.name);
let member_name = self.interner.resolve(ma.member); let member_name = self.interner.resolve(ma.member);
// 1) Prefer exact contract registry mapping (Pad/Touch services, etc.)
if let Some(method) = self.contract_registry.get_method(obj_name, member_name) {
self.convert_pbs_type(&method.return_type)
} else {
// 2) Legacy snapshot helpers
match (obj_name, member_name) { match (obj_name, member_name) {
("Input", "pad") => Type::Struct("Pad".to_string()), ("Input", "pad") => Type::Struct("Pad".to_string()),
("Input", "touch") => Type::Struct("Touch".to_string()), ("Input", "touch") => Type::Struct("Touch".to_string()),
_ => Type::Int, _ => Type::Int,
} }
}
} else { } else {
Type::Int Type::Int
} }
@ -747,6 +794,39 @@ impl<'a> Lowerer<'a> {
return Ok(()); return Ok(());
} }
// Fallback: Handle member access where the object is a Call to a host contract method
// Example: Pad.a().down — object is a Call(MemberAccess(Pad, a))
if let NodeKind::Call(call_node) = self.arena.kind(n.object) {
if let NodeKind::MemberAccess(inner_ma) = self.arena.kind(call_node.callee) {
if let NodeKind::Ident(obj_id) = self.arena.kind(inner_ma.object) {
let contract_name = self.interner.resolve(obj_id.name);
let method_name = self.interner.resolve(inner_ma.member);
if let Some(method) = self.contract_registry.get_method(contract_name, method_name) {
// Determine return type and slots
let ret_ty = self.convert_pbs_type(&method.return_type);
let slots = self.get_type_slots(&ret_ty);
if let Type::Struct(struct_name) = &ret_ty {
// Lower the call to push all slots
self.lower_call(n.object, &call_node)?;
// Store into a temp local to avoid leaving extra slots on the operand stack
let tmp_slot = self.add_local_to_scope(
format!("$tmp_{}", self.get_next_local_slot()),
ret_ty.clone(),
);
for i in (0..slots).rev() {
self.emit(InstrKind::SetLocal(tmp_slot + i));
}
// Load only the requested field
let field_name = self.interner.resolve(n.member);
let field_off = self.get_field_offset(struct_name, field_name);
self.emit(InstrKind::GetLocal(tmp_slot + field_off));
return Ok(());
}
}
}
}
}
Ok(()) Ok(())
} }
@ -789,6 +869,14 @@ impl<'a> Lowerer<'a> {
} }
} }
match struct_name { match struct_name {
// New `Button` mirrors legacy `ButtonState` offsets
"Button" => match field_name {
"pressed" => 0,
"released" => 1,
"down" => 2,
"hold_frames" => 3,
_ => 0,
},
"ButtonState" => match field_name { "ButtonState" => match field_name {
"pressed" => 0, "pressed" => 0,
"released" => 1, "released" => 1,
@ -828,13 +916,20 @@ impl<'a> Lowerer<'a> {
} }
} }
match struct_name { match struct_name {
"Pad" => Type::Struct("ButtonState".to_string()), // Pad's per-button service returns a `Button` in the new API
"Pad" => Type::Struct("Button".to_string()),
// Field types for `Button` mirror legacy `ButtonState`
"Button" => match field_name {
"hold_frames" => Type::Bounded,
_ => Type::Bool,
},
"ButtonState" => match field_name { "ButtonState" => match field_name {
"hold_frames" => Type::Bounded, "hold_frames" => Type::Bounded,
_ => Type::Bool, _ => Type::Bool,
}, },
"Touch" => match field_name { "Touch" => match field_name {
"f" => Type::Struct("ButtonState".to_string()), // Touch.f() now returns a `Button`
"f" => Type::Struct("Button".to_string()),
_ => Type::Int, _ => Type::Int,
}, },
_ => Type::Int, _ => Type::Int,
@ -943,6 +1038,46 @@ impl<'a> Lowerer<'a> {
} }
} }
NodeKind::MemberAccess(ma) => { NodeKind::MemberAccess(ma) => {
// Special-case: Member access over a call expression that returns a struct from a host contract,
// e.g., Pad.a().down — we must:
// 1) lower the call (pushing all struct slots),
// 2) store it into a temporary local (all slots),
// 3) load only the requested field slot back to the stack.
if let NodeKind::Call(call_node) = self.arena.kind(ma.object) {
// Try to resolve if callee is a host contract method to infer return struct and slots
if let NodeKind::MemberAccess(inner_ma) = self.arena.kind(call_node.callee) {
if let NodeKind::Ident(obj_id) = self.arena.kind(inner_ma.object) {
let contract_name = self.interner.resolve(obj_id.name);
let method_name = self.interner.resolve(inner_ma.member);
if let Some(method) = self.contract_registry.get_method(contract_name, method_name) {
// Determine slots from the declared return type BEFORE lowering the call
let ret_ty = self.convert_pbs_type(&method.return_type);
let slots = self.get_type_slots(&ret_ty);
let struct_name = match &ret_ty { Type::Struct(s) => s.clone(), _ => String::new() };
// Lower the call first (this will push all return slots from HostCall)
self.lower_call(n.callee, &call_node)?;
// Allocate a temp local to capture the struct
let tmp_slot = self.add_local_to_scope(
format!("$tmp_{}", self.get_next_local_slot()),
ret_ty.clone(),
);
// Store all slots (top of stack has the last slot). We must store in reverse order.
for i in (0..slots).rev() {
self.emit(InstrKind::SetLocal(tmp_slot + i));
}
// Compute field offset/type and load only that field
let field_name = self.interner.resolve(ma.member);
let field_off = self.get_field_offset(&struct_name, field_name);
let _field_ty = self.get_field_type(&struct_name, field_name);
self.emit(InstrKind::GetLocal(tmp_slot + field_off));
return Ok(());
}
}
}
}
// Check if it's a constructor alias: TypeName.Alias(...) // Check if it's a constructor alias: TypeName.Alias(...)
let ctor = if let NodeKind::Ident(obj_id) = self.arena.kind(ma.object) { let ctor = if let NodeKind::Ident(obj_id) = self.arena.kind(ma.object) {
let obj_name = self.interner.resolve(obj_id.name); let obj_name = self.interner.resolve(obj_id.name);
@ -995,10 +1130,17 @@ impl<'a> Lowerer<'a> {
} }
if let Some(method) = self.contract_registry.get_method(obj_name, member_name) { if let Some(method) = self.contract_registry.get_method(obj_name, member_name) {
let id = method.id; let id = method.id;
let return_slots = if matches!(method.return_type, PbsType::Void) { // Compute return slots from declared return type. Structs may span >1 slot (e.g., Button=4)
0 let return_slots = match &method.return_type {
} else { PbsType::Void | PbsType::None => 0,
1 PbsType::Struct(name) => {
// Prefer builtin struct slots, then fallback to struct_slots map, default 1
if let Some(bi) = self.get_builtin_struct_slots(name) { bi } else { *self.struct_slots.get(name).unwrap_or(&1) }
}
other => {
let ty = self.convert_pbs_type(other);
self.get_type_slots(&ty)
}
}; };
self.emit(InstrKind::HostCall(id, return_slots)); self.emit(InstrKind::HostCall(id, return_slots));
return Ok(()); return Ok(());

View File

@ -362,7 +362,8 @@ impl<'a> TypeChecker<'a> {
_ => {} _ => {}
} }
} }
"ButtonState" => { // New `Button` mirrors legacy `ButtonState` for field typing
"Button" | "ButtonState" => {
match member_str { match member_str {
"pressed" | "released" | "down" => return PbsType::Bool, "pressed" | "released" | "down" => return PbsType::Bool,
"hold_frames" => return PbsType::Bounded, "hold_frames" => return PbsType::Bounded,
@ -372,7 +373,7 @@ impl<'a> TypeChecker<'a> {
"Pad" => { "Pad" => {
match member_str { match member_str {
"up" | "down" | "left" | "right" | "a" | "b" | "x" | "y" | "l" | "r" | "start" | "select" => { "up" | "down" | "left" | "right" | "a" | "b" | "x" | "y" | "l" | "r" | "start" | "select" => {
return PbsType::Struct("ButtonState".to_string()); return PbsType::Struct("Button".to_string());
} }
"any" => { "any" => {
return PbsType::Function { return PbsType::Function {
@ -385,7 +386,7 @@ impl<'a> TypeChecker<'a> {
} }
"Touch" => { "Touch" => {
match member_str { match member_str {
"f" => return PbsType::Struct("ButtonState".to_string()), "f" => return PbsType::Struct("Button".to_string()),
"x" | "y" => return PbsType::Int, "x" | "y" => return PbsType::Int,
_ => {} _ => {}
} }

View File

@ -4,11 +4,11 @@ pub mod gfx_bridge;
pub mod hardware_bridge; pub mod hardware_bridge;
pub mod host_context; pub mod host_context;
pub mod host_return; pub mod host_return;
pub mod input_signals;
pub mod native_interface; pub mod native_interface;
pub mod pad_bridge; pub mod pad_bridge;
pub mod touch_bridge; pub mod touch_bridge;
pub mod native_helpers; pub mod native_helpers;
pub mod input_signals;
pub use asset_bridge::AssetBridge; pub use asset_bridge::AssetBridge;
pub use audio_bridge::{AudioBridge, LoopMode}; pub use audio_bridge::{AudioBridge, LoopMode};

View File

@ -5,11 +5,7 @@ edition = "2024"
license.workspace = true license.workspace = true
[dependencies] [dependencies]
serde = { version = "1.0.228", features = ["derive"] }
serde_json = "1.0.149" serde_json = "1.0.149"
prometeu-vm = { path = "../prometeu-vm" } prometeu-vm = { path = "../prometeu-vm" }
prometeu-kernel = { path = "../prometeu-kernel" }
prometeu-bytecode = { path = "../prometeu-bytecode" }
prometeu-abi = { path = "../prometeu-abi" } prometeu-abi = { path = "../prometeu-abi" }
prometeu-hardware-contract = { path = "../prometeu-hardware-contract" } prometeu-hardware-contract = { path = "../prometeu-hardware-contract" }
url = "2.5.8"

View File

@ -11,4 +11,3 @@ pub use crate::audio::{Audio, AudioCommand, Channel, MAX_CHANNELS, OUTPUT_SAMPLE
pub use crate::gfx::Gfx; pub use crate::gfx::Gfx;
pub use crate::memory_banks::MemoryBanks; pub use crate::memory_banks::MemoryBanks;
pub use crate::pad::Pad; pub use crate::pad::Pad;
pub use crate::touch::Touch;

View File

@ -1,4 +1,4 @@
use prometeu_abi::model::Button; use prometeu_abi::model::{Button};
use prometeu_hardware_contract::{InputSignals, TouchBridge}; use prometeu_hardware_contract::{InputSignals, TouchBridge};
#[derive(Default, Clone, Copy, Debug)] #[derive(Default, Clone, Copy, Debug)]
@ -8,13 +8,6 @@ pub struct Touch {
pub y: i32, pub y: i32,
} }
impl TouchBridge for Touch {
fn begin_frame(&mut self, signals: &InputSignals) { self.begin_frame(signals) }
fn f(&self) -> &Button { &self.f }
fn x(&self) -> i32 { self.x }
fn y(&self) -> i32 { self.y }
}
impl Touch { impl Touch {
/// Transient flags should last only 1 frame. /// Transient flags should last only 1 frame.
pub fn begin_frame(&mut self, signals: &InputSignals) { pub fn begin_frame(&mut self, signals: &InputSignals) {
@ -23,3 +16,10 @@ impl Touch {
self.y = signals.y_pos.clone(); self.y = signals.y_pos.clone();
} }
} }
impl TouchBridge for Touch {
fn begin_frame(&mut self, signals: &InputSignals) { self.begin_frame(signals) }
fn f(&self) -> &Button { &self.f }
fn x(&self) -> i32 { self.x }
fn y(&self) -> i32 { self.y }
}

View File

@ -1066,6 +1066,15 @@ impl NativeInterface for PrometeuOS {
ret.push_int(hw.touch().f().hold_frames as i64); ret.push_int(hw.touch().f().hold_frames as i64);
Ok(()) Ok(())
} }
Syscall::TouchGetFinger => {
let btn = hw.touch().f();
// Return as 4 slots to mirror snapshot order
ret.push_bool(btn.pressed);
ret.push_bool(btn.released);
ret.push_bool(btn.down);
ret.push_int(btn.hold_frames as i64);
Ok(())
}
Syscall::InputPadSnapshot => { Syscall::InputPadSnapshot => {
let pad = hw.pad(); let pad = hw.pad();
for btn in [ for btn in [
@ -1091,6 +1100,104 @@ impl NativeInterface for PrometeuOS {
Ok(()) Ok(())
} }
// --- Pad per-button service-based syscalls (return Button as 4 slots) ---
Syscall::PadGetUp => {
let b = hw.pad().up();
ret.push_bool(b.pressed);
ret.push_bool(b.released);
ret.push_bool(b.down);
ret.push_int(b.hold_frames as i64);
Ok(())
}
Syscall::PadGetDown => {
let b = hw.pad().down();
ret.push_bool(b.pressed);
ret.push_bool(b.released);
ret.push_bool(b.down);
ret.push_int(b.hold_frames as i64);
Ok(())
}
Syscall::PadGetLeft => {
let b = hw.pad().left();
ret.push_bool(b.pressed);
ret.push_bool(b.released);
ret.push_bool(b.down);
ret.push_int(b.hold_frames as i64);
Ok(())
}
Syscall::PadGetRight => {
let b = hw.pad().right();
ret.push_bool(b.pressed);
ret.push_bool(b.released);
ret.push_bool(b.down);
ret.push_int(b.hold_frames as i64);
Ok(())
}
Syscall::PadGetA => {
let b = hw.pad().a();
ret.push_bool(b.pressed);
ret.push_bool(b.released);
ret.push_bool(b.down);
ret.push_int(b.hold_frames as i64);
Ok(())
}
Syscall::PadGetB => {
let b = hw.pad().b();
ret.push_bool(b.pressed);
ret.push_bool(b.released);
ret.push_bool(b.down);
ret.push_int(b.hold_frames as i64);
Ok(())
}
Syscall::PadGetX => {
let b = hw.pad().x();
ret.push_bool(b.pressed);
ret.push_bool(b.released);
ret.push_bool(b.down);
ret.push_int(b.hold_frames as i64);
Ok(())
}
Syscall::PadGetY => {
let b = hw.pad().y();
ret.push_bool(b.pressed);
ret.push_bool(b.released);
ret.push_bool(b.down);
ret.push_int(b.hold_frames as i64);
Ok(())
}
Syscall::PadGetL => {
let b = hw.pad().l();
ret.push_bool(b.pressed);
ret.push_bool(b.released);
ret.push_bool(b.down);
ret.push_int(b.hold_frames as i64);
Ok(())
}
Syscall::PadGetR => {
let b = hw.pad().r();
ret.push_bool(b.pressed);
ret.push_bool(b.released);
ret.push_bool(b.down);
ret.push_int(b.hold_frames as i64);
Ok(())
}
Syscall::PadGetStart => {
let b = hw.pad().start();
ret.push_bool(b.pressed);
ret.push_bool(b.released);
ret.push_bool(b.down);
ret.push_int(b.hold_frames as i64);
Ok(())
}
Syscall::PadGetSelect => {
let b = hw.pad().select();
ret.push_bool(b.pressed);
ret.push_bool(b.released);
ret.push_bool(b.down);
ret.push_int(b.hold_frames as i64);
Ok(())
}
// --- Audio Syscalls --- // --- Audio Syscalls ---
// audio.play_sample(sample_id, voice_id, volume, pan, pitch) // audio.play_sample(sample_id, voice_id, volume, pan, pitch)

View File

@ -0,0 +1,8 @@
pub declare struct Color(raw: bounded)
[[
BLACK: Color(0b),
WHITE: Color(65535b),
RED: Color(63488b),
GREEN: Color(2016b),
BLUE: Color(31b)
]]

View File

@ -1,12 +1,3 @@
pub declare struct Color(raw: bounded)
[[
BLACK: Color(0b),
WHITE: Color(65535b),
RED: Color(63488b),
GREEN: Color(2016b),
BLUE: Color(31b)
]]
pub declare contract Gfx host { pub declare contract Gfx host {
fn clear(color: Color): void; fn clear(color: Color): void;
} }

View File

@ -0,0 +1,6 @@
pub declare struct Button(
pressed: bool,
released: bool,
down: bool,
hold_frames: bounded
)

View File

@ -1,25 +1,20 @@
pub declare struct ButtonState( pub declare contract Pad host {
pressed: bool, fn up(): Button;
released: bool, fn down(): Button;
down: bool, fn left(): Button;
hold_frames: bounded fn right(): Button;
) fn a(): Button;
fn b(): Button;
pub declare struct Pad( fn x(): Button;
up: ButtonState, fn y(): Button;
down: ButtonState, fn l(): Button;
left: ButtonState, fn r(): Button;
right: ButtonState, fn start(): Button;
a: ButtonState, fn select(): Button;
b: ButtonState, }
x: ButtonState,
y: ButtonState, pub declare contract Touch host {
l: ButtonState, fn screen_x(): int;
r: ButtonState, fn screen_y(): int;
start: ButtonState, fn finger(): Button;
select: ButtonState
)
pub declare contract Input host {
fn pad(): Pad;
} }

View File

@ -1,5 +1,5 @@
import { Color, Gfx } from "@sdk:gfx"; import { Color, Gfx } from "@sdk:gfx";
import { Input } from "@sdk:input"; import { Pad, Touch } from "@sdk:input";
declare struct Vec2(x: int, y: int) declare struct Vec2(x: int, y: int)
[ [
@ -51,8 +51,10 @@ fn frame(): void {
} }
// 4. Input Snapshot & Nested Member Access // 4. Input Snapshot & Nested Member Access
let p = Input.pad(); let pad = Pad.a();
if p.a.down { let touch = Touch.finger();
let is_pressed = pad.down || touch.down;
if is_pressed {
Gfx.clear(Color.BLUE); Gfx.clear(Color.BLUE);
} }
} }