Phase 4: Steuerelemente implementieren
This commit is contained in:
@@ -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 0–11.
|
||||
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]
|
||||
|
||||
@@ -24,6 +24,9 @@ use unicode_width::UnicodeWidthChar;
|
||||
pub const MIN_COLS: usize = 80;
|
||||
pub const MIN_ROWS: usize = 25;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct ScreenError;
|
||||
|
||||
/// Eine Bildschirmzelle: Zeichen plus klassisches Farbattribut.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct Cell {
|
||||
@@ -78,6 +81,9 @@ pub struct TextScreen {
|
||||
/// Seit der letzten Anzeige verändert? Der Host wird nur dann zum
|
||||
/// Neuzeichnen aufgefordert.
|
||||
veraendert: bool,
|
||||
graphic_x: i32,
|
||||
graphic_y: i32,
|
||||
graphic_view: Option<(i32, i32, i32, i32)>,
|
||||
}
|
||||
|
||||
impl Default for TextScreen {
|
||||
@@ -110,6 +116,9 @@ impl TextScreen {
|
||||
view_full: true,
|
||||
belegt: vec![None; rows],
|
||||
veraendert: true,
|
||||
graphic_x: 0,
|
||||
graphic_y: 0,
|
||||
graphic_view: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,8 +155,8 @@ impl TextScreen {
|
||||
}
|
||||
self.cells = cells;
|
||||
let mut belegt = vec![None; rows];
|
||||
for r in 0..self.rows.min(rows) {
|
||||
belegt[r] = self.belegt[r].map(|c| c.min(cols - 1));
|
||||
for (r, value) in belegt.iter_mut().enumerate().take(self.rows.min(rows)) {
|
||||
*value = self.belegt[r].map(|c| c.min(cols - 1));
|
||||
}
|
||||
self.belegt = belegt;
|
||||
self.cols = cols;
|
||||
@@ -225,9 +234,9 @@ impl TextScreen {
|
||||
}
|
||||
|
||||
/// `LOCATE zeile, spalte` (1-basiert); außerhalb → Err (Fehler 5).
|
||||
pub fn locate(&mut self, row: usize, col: usize) -> Result<(), ()> {
|
||||
pub fn locate(&mut self, row: usize, col: usize) -> Result<(), ScreenError> {
|
||||
if row < 1 || row > self.rows || col < 1 || col > self.cols {
|
||||
return Err(());
|
||||
return Err(ScreenError);
|
||||
}
|
||||
self.cur_row = row - 1;
|
||||
// Auf eine Fortsetzungszelle zu zeigen bedeutet den Zeichenanfang.
|
||||
@@ -251,9 +260,9 @@ impl TextScreen {
|
||||
}
|
||||
|
||||
/// `VIEW PRINT oben TO unten` (1-basiert).
|
||||
pub fn view_print(&mut self, top: usize, bottom: usize) -> Result<(), ()> {
|
||||
pub fn view_print(&mut self, top: usize, bottom: usize) -> Result<(), ScreenError> {
|
||||
if top < 1 || bottom > self.rows || top > bottom {
|
||||
return Err(());
|
||||
return Err(ScreenError);
|
||||
}
|
||||
self.view_top = top - 1;
|
||||
self.view_bottom = bottom - 1;
|
||||
@@ -265,6 +274,122 @@ impl TextScreen {
|
||||
self.cells[(row - 1) * self.cols + (col - 1)]
|
||||
}
|
||||
|
||||
pub fn graphics_position(&self) -> (i32, i32) {
|
||||
(self.graphic_x, self.graphic_y)
|
||||
}
|
||||
|
||||
pub fn graphics_view(&mut self, view: Option<(i32, i32, i32, i32)>) -> Result<(), ScreenError> {
|
||||
if view.is_some_and(|(x1, y1, x2, y2)| x1 < 0 || y1 < 0 || x1 > x2 || y1 > y2) {
|
||||
return Err(ScreenError);
|
||||
}
|
||||
self.graphic_view = view;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn graphics_cell(&mut self, x: i32, y: i32, color: i32) {
|
||||
if x < 0
|
||||
|| y < 0
|
||||
|| self
|
||||
.graphic_view
|
||||
.is_some_and(|(x1, y1, x2, y2)| x < x1 || x > x2 || y < y1 || y > y2)
|
||||
{
|
||||
return;
|
||||
}
|
||||
let (col, row) = (x as usize / 8, y as usize / 8);
|
||||
if col >= self.cols || row >= self.rows {
|
||||
return;
|
||||
}
|
||||
self.cells[row * self.cols + col] = Cell {
|
||||
ch: if color == 0 { ' ' } else { '█' },
|
||||
fg: color.clamp(0, 15) as u8,
|
||||
bg: self.bg,
|
||||
fortsetzung: false,
|
||||
};
|
||||
self.belegt[row] = Some(self.belegt[row].map_or(col, |last| last.max(col)));
|
||||
self.veraendert = true;
|
||||
}
|
||||
|
||||
pub fn graphics_line(&mut self, from: (i32, i32), to: (i32, i32), color: i32, fill: u8) {
|
||||
let (x1, y1) = from;
|
||||
let (x2, y2) = to;
|
||||
if fill == 2 {
|
||||
for y in (y1.min(y2)..=y1.max(y2)).step_by(8) {
|
||||
for x in (x1.min(x2)..=x1.max(x2)).step_by(8) {
|
||||
self.graphics_cell(x, y, color);
|
||||
}
|
||||
}
|
||||
} else if fill == 1 {
|
||||
self.graphics_segment(x1, y1, x2, y1, color);
|
||||
self.graphics_segment(x2, y1, x2, y2, color);
|
||||
self.graphics_segment(x2, y2, x1, y2, color);
|
||||
self.graphics_segment(x1, y2, x1, y1, color);
|
||||
} else {
|
||||
self.graphics_segment(x1, y1, x2, y2, color);
|
||||
}
|
||||
self.graphic_x = x2;
|
||||
self.graphic_y = y2;
|
||||
}
|
||||
|
||||
fn graphics_segment(&mut self, mut x: i32, mut y: i32, x2: i32, y2: i32, color: i32) {
|
||||
let dx = (x2 - x).abs();
|
||||
let sx = if x < x2 { 1 } else { -1 };
|
||||
let dy = -(y2 - y).abs();
|
||||
let sy = if y < y2 { 1 } else { -1 };
|
||||
let mut error = dx + dy;
|
||||
loop {
|
||||
self.graphics_cell(x, y, color);
|
||||
if x == x2 && y == y2 {
|
||||
break;
|
||||
}
|
||||
let twice = error * 2;
|
||||
if twice >= dy {
|
||||
error += dy;
|
||||
x += sx;
|
||||
}
|
||||
if twice <= dx {
|
||||
error += dx;
|
||||
y += sy;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn graphics_paint(&mut self, x: i32, y: i32, color: i32) {
|
||||
let (start_col, start_row) = (x.max(0) as usize / 8, y.max(0) as usize / 8);
|
||||
if start_col >= self.cols || start_row >= self.rows {
|
||||
return;
|
||||
}
|
||||
let target = self.cells[start_row * self.cols + start_col].ch;
|
||||
let replacement = if color == 0 { ' ' } else { '█' };
|
||||
if target == replacement {
|
||||
return;
|
||||
}
|
||||
let mut pending = vec![(start_col, start_row)];
|
||||
while let Some((col, row)) = pending.pop() {
|
||||
let px = col as i32 * 8;
|
||||
let py = row as i32 * 8;
|
||||
if col >= self.cols
|
||||
|| row >= self.rows
|
||||
|| self.cells[row * self.cols + col].ch != target
|
||||
|| self
|
||||
.graphic_view
|
||||
.is_some_and(|(x1, y1, x2, y2)| px < x1 || px > x2 || py < y1 || py > y2)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
self.graphics_cell(px, py, color);
|
||||
if col > 0 {
|
||||
pending.push((col - 1, row));
|
||||
}
|
||||
if row > 0 {
|
||||
pending.push((col, row - 1));
|
||||
}
|
||||
pending.push((col + 1, row));
|
||||
pending.push((col, row + 1));
|
||||
}
|
||||
self.graphic_x = x;
|
||||
self.graphic_y = y;
|
||||
}
|
||||
|
||||
/// Text an der Cursorposition ausgeben: Umbruch am rechten Rand,
|
||||
/// Scrollen am unteren Rand des Scrollbereichs. `\n` bricht um,
|
||||
/// `\r` setzt an den Zeilenanfang.
|
||||
|
||||
@@ -316,7 +316,7 @@ fn tausender_gruppieren(s: &str) -> String {
|
||||
let z: Vec<char> = s.chars().collect();
|
||||
let mut out = String::new();
|
||||
for (i, c) in z.iter().enumerate() {
|
||||
if i > 0 && (z.len() - i) % 3 == 0 {
|
||||
if i > 0 && (z.len() - i).is_multiple_of(3) {
|
||||
out.push(',');
|
||||
}
|
||||
out.push(*c);
|
||||
@@ -433,7 +433,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn numerisches_feld_mit_nachkommastellen() {
|
||||
assert_eq!(u("###.##", &[z(3.14159)]), " 3.14");
|
||||
assert_eq!(u("###.##", &[z(3.126)]), " 3.13");
|
||||
assert_eq!(u("##.##", &[z(-1.5)]), "-1.50");
|
||||
}
|
||||
|
||||
|
||||
@@ -199,9 +199,7 @@ pub fn cur_to_i64(c: i64) -> i64 {
|
||||
// r in 0..10000; runde halb-zu-gerade
|
||||
if r > 5_000 {
|
||||
q + 1
|
||||
} else if r < 5_000 {
|
||||
q
|
||||
} else if q % 2 == 0 {
|
||||
} else if r < 5_000 || q % 2 == 0 {
|
||||
q
|
||||
} else {
|
||||
q + 1
|
||||
|
||||
Reference in New Issue
Block a user