Phase 4: Steuerelemente implementieren

This commit is contained in:
2026-09-05 12:49:46 +02:00
parent 1cb6ae8fd9
commit 19804e0e2d
36 changed files with 6472 additions and 689 deletions

View File

@@ -63,6 +63,7 @@ pub struct RtState {
rng: u32,
rnd_last: f32,
pub command: String,
pub clipboard: String,
}
impl Default for RtState {
@@ -93,6 +94,7 @@ impl Default for RtState {
rng: 0x50000,
rnd_last: 0.0,
command: String::new(),
clipboard: String::new(),
}
}
}
@@ -438,7 +440,14 @@ pub mod ids {
pub const ISAM_BOF: u16 = 147;
/// `SetUEvent` — loest das benutzerdefinierte Ereignis aus (§8).
pub const SETUEVENT: u16 = 148;
pub const COUNT: u16 = 149;
pub const MSGBOX: u16 = 149;
pub const INPUTBOX_S: u16 = 150;
pub const CLIPBOARD_ADD: u16 = 151;
pub const CLIPBOARD_GET: u16 = 152;
pub const GRAPHICS_LINE: u16 = 153;
pub const GRAPHICS_PAINT: u16 = 154;
pub const GRAPHICS_VIEW: u16 = 155;
pub const COUNT: u16 = 156;
}
/// Dispatch-Tabelle in Index-Reihenfolge.
@@ -594,11 +603,35 @@ pub fn builtin_table() -> &'static [BuiltinFn] {
bi_isam_setmem,
bi_isam_bof,
bi_setuevent,
bi_msgbox,
bi_inputbox,
bi_clipboard_add,
bi_clipboard_get,
bi_graphics_line,
bi_graphics_paint,
bi_graphics_view,
];
debug_assert_eq!(TABLE.len(), ids::COUNT as usize);
TABLE
}
fn bi_clipboard_add(
st: &mut RtState,
_: &mut dyn Host,
a: &mut [Value],
) -> Result<Option<Value>, RuntimeError> {
st.clipboard.push_str(&arg_str(a, 0)?);
Ok(None)
}
fn bi_clipboard_get(
st: &mut RtState,
_: &mut dyn Host,
_: &mut [Value],
) -> Result<Option<Value>, RuntimeError> {
Ok(Some(Value::Str(Rc::from(st.clipboard.as_str()))))
}
// ---- Argument-Hilfen --------------------------------------------------------
fn arg_str(args: &[Value], i: usize) -> Result<Rc<str>, RuntimeError> {
@@ -1881,19 +1914,92 @@ fn bi_view_print(
}
fn bi_screen_stmt(
_: &mut RtState,
st: &mut RtState,
_: &mut dyn Host,
a: &mut [Value],
) -> Result<Option<Value>, RuntimeError> {
// `SCREEN modus[, farbschalter[, aktiv[, sichtbar]]]`. Nur der Textmodus 0
// existiert; Grafikmodi sind deklariertes Non-Feature. Die übrigen
// Argumente betreffen Grafikseiten und bleiben folgenlos.
// Die Terminaldarstellung bildet die historischen Pixelmodi auf Zellen ab;
// Seitenargumente haben im einzelnen Puffer keine zusätzliche Wirkung.
match opt(a, 0)? {
None | Some(0) => Ok(None),
Some(_) => Err(RuntimeError(73)), // Advanced feature unavailable
None | Some(0..=13) => {
st.screen.graphics_view(None).unwrap();
st.screen.cls();
Ok(None)
}
Some(_) => Err(RuntimeError::ILLEGAL_FUNCTION_CALL),
}
}
fn bi_graphics_line(
st: &mut RtState,
_: &mut dyn Host,
a: &mut [Value],
) -> Result<Option<Value>, RuntimeError> {
let mut from = (arg_i32(a, 0)?, arg_i32(a, 1)?);
if from.0 == i32::MIN {
from = st.screen.graphics_position();
}
let mut to = (arg_i32(a, 2)?, arg_i32(a, 3)?);
if arg_i32(a, 5)? != 0 {
to.0 += from.0;
to.1 += from.1;
}
let color = match arg_i32(a, 4)? {
-1 => i32::from(st.screen.fg),
color @ 0..=15 => color,
_ => return Err(RuntimeError::ILLEGAL_FUNCTION_CALL),
};
st.screen
.graphics_line(from, to, color, arg_i32(a, 6)? as u8);
Ok(None)
}
fn bi_graphics_paint(
st: &mut RtState,
_: &mut dyn Host,
a: &mut [Value],
) -> Result<Option<Value>, RuntimeError> {
let color = match a.get(2) {
Some(Value::Int(v)) => i32::from(*v),
Some(Value::Lng(v)) => *v,
Some(Value::Str(_)) | None => i32::from(st.screen.fg),
_ => return Err(RuntimeError::TYPE_MISMATCH),
};
if !(0..=15).contains(&color) {
return Err(RuntimeError::ILLEGAL_FUNCTION_CALL);
}
st.screen
.graphics_paint(arg_i32(a, 0)?, arg_i32(a, 1)?, color);
Ok(None)
}
fn bi_graphics_view(
st: &mut RtState,
_: &mut dyn Host,
a: &mut [Value],
) -> Result<Option<Value>, RuntimeError> {
let x1 = arg_i32(a, 0)?;
if x1 < 0 {
st.screen.graphics_view(None).unwrap();
return Ok(None);
}
let view = (x1, arg_i32(a, 1)?, arg_i32(a, 2)?, arg_i32(a, 3)?);
st.screen
.graphics_view(Some(view))
.map_err(|_| RuntimeError::ILLEGAL_FUNCTION_CALL)?;
let fill = arg_i32(a, 4)?;
if fill >= 0 {
st.screen
.graphics_line((view.0, view.1), (view.2, view.3), fill, 2);
}
let border = arg_i32(a, 5)?;
if border >= 0 {
st.screen
.graphics_line((view.0, view.1), (view.2, view.3), border, 1);
}
Ok(None)
}
/// Nummer eines Funktionstasten-Makros → Index 011.
fn key_index(n: i32) -> Option<usize> {
match n {
@@ -2016,6 +2122,14 @@ fn bi_input_s(
if n < 0 {
return Err(RuntimeError::ILLEGAL_FUNCTION_CALL);
}
if a.len() > 1 {
let nummer = arg_i32(a, 1)?;
let datei = st.dateien.get(nummer)?;
let bytes = datei.bytes_lesen(datei.position, n as usize)?;
return Ok(Some(Value::Str(Rc::from(
String::from_utf8_lossy(&bytes).as_ref(),
))));
}
// Blockierend, ohne Echo auf dem Bildschirm.
let mut s = String::new();
for _ in 0..n {
@@ -2546,6 +2660,22 @@ fn bi_isam_bof(
Ok(Some(Value::Int(if st.isam.bof(n) { -1 } else { 0 })))
}
fn bi_msgbox(
_: &mut RtState,
_: &mut dyn Host,
_: &mut [Value],
) -> Result<Option<Value>, RuntimeError> {
Err(RuntimeError::FEATURE_UNAVAILABLE)
}
fn bi_inputbox(
_: &mut RtState,
_: &mut dyn Host,
_: &mut [Value],
) -> Result<Option<Value>, RuntimeError> {
Err(RuntimeError::FEATURE_UNAVAILABLE)
}
#[cfg(test)]
mod tests {
use super::*;
@@ -2635,7 +2765,7 @@ mod tests {
h.ereignis(Ereignis::Signal(1));
st.pump(&mut h, false);
assert_eq!(st.traps.naechstes(), Some((Quelle::Signal(1), 22)));
assert!(matches!(h.next_event(false), None));
assert!(h.next_event(false).is_none());
}
#[test]