Ereigniszustellung und Hostgrenzen korrigieren und Change archivieren
This commit is contained in:
@@ -1777,6 +1777,49 @@ impl FormsModel {
|
||||
}
|
||||
}
|
||||
|
||||
fn timer_enabled(&self, key: ObjectKey) -> bool {
|
||||
self.instance(key)
|
||||
.is_ok_and(|obj| obj.description.class == ObjectClass::Timer)
|
||||
&& self.boolean(key, "ENABLED")
|
||||
&& self.integer(key, "INTERVAL").unwrap_or(0) > 0
|
||||
&& self
|
||||
.root_form(key)
|
||||
.is_some_and(|form| self.is_visible(form))
|
||||
}
|
||||
|
||||
/// Zeitbedarf für Timer und gedrückt gehaltene Spin-Schaltflächen.
|
||||
pub fn next_deadline(&self) -> Option<u64> {
|
||||
if self.menu_is_open() || (!self.has_visible_forms() && self.spin_repeat.is_none()) {
|
||||
return None;
|
||||
}
|
||||
let timers = self
|
||||
.keys()
|
||||
.into_iter()
|
||||
.filter(|key| self.timer_enabled(*key))
|
||||
.map(|key| {
|
||||
self.timer_last
|
||||
.get(&key)
|
||||
.copied()
|
||||
.unwrap_or(0)
|
||||
.saturating_add(self.integer(key, "INTERVAL").unwrap() as u64)
|
||||
});
|
||||
let spin = self.spin_repeat.and_then(|(key, _)| {
|
||||
let interval = self.integer(key, "INTERVAL").unwrap_or(250).max(0) as u64;
|
||||
(interval > 0).then(|| {
|
||||
self.spin_last
|
||||
.map_or(0, |last| last.saturating_add(interval))
|
||||
})
|
||||
});
|
||||
timers.chain(spin).min()
|
||||
}
|
||||
|
||||
pub fn mouse_needs_time(&self, event: MausEreignis) -> bool {
|
||||
self.active_form.is_some()
|
||||
&& !self.menu_is_open()
|
||||
&& event.art == MausArt::Loslassen
|
||||
&& self.pressed.is_some()
|
||||
}
|
||||
|
||||
pub fn timers(&mut self, now_ms: u64) {
|
||||
if !self.menu_path.is_empty() {
|
||||
return;
|
||||
@@ -1784,15 +1827,7 @@ impl FormsModel {
|
||||
let mut due: Vec<_> = self
|
||||
.keys()
|
||||
.into_iter()
|
||||
.filter(|key| {
|
||||
self.instance(*key)
|
||||
.is_ok_and(|obj| obj.description.class == ObjectClass::Timer)
|
||||
&& self.boolean(*key, "ENABLED")
|
||||
&& self.integer(*key, "INTERVAL").unwrap_or(0) > 0
|
||||
&& self
|
||||
.root_form(*key)
|
||||
.is_some_and(|form| self.is_visible(form))
|
||||
})
|
||||
.filter(|key| self.timer_enabled(*key))
|
||||
.collect();
|
||||
due.sort_by_key(|key| {
|
||||
self.instance(*key)
|
||||
@@ -2395,13 +2430,16 @@ fn dialog_event(
|
||||
model: &mut FormsModel,
|
||||
screen: &mut TextScreen,
|
||||
host: &mut dyn Host,
|
||||
queued: &mut VecDeque<Ereignis>,
|
||||
queued: &mut VecDeque<tb_runtime::builtins::Eingabe>,
|
||||
plain_access_key: bool,
|
||||
) -> Option<FormEvent> {
|
||||
loop {
|
||||
model.render(screen);
|
||||
host.present(screen);
|
||||
let event = queued.pop_front().or_else(|| host.next_event(true))?;
|
||||
let event = queued
|
||||
.pop_front()
|
||||
.map(|e| e.ereignis)
|
||||
.or_else(|| host.next_event(true))?;
|
||||
match event {
|
||||
Ereignis::Taste(key, shift) => {
|
||||
let shift = if plain_access_key && key.chars().count() == 1 {
|
||||
@@ -2412,14 +2450,19 @@ fn dialog_event(
|
||||
model.handle_key(&key, shift);
|
||||
}
|
||||
Ereignis::Maus(event) => {
|
||||
model.handle_mouse_at(event, host.jetzt_ms());
|
||||
let now = if model.mouse_needs_time(event) {
|
||||
host.jetzt_ms()
|
||||
} else {
|
||||
0
|
||||
};
|
||||
model.handle_mouse_at(event, now);
|
||||
}
|
||||
Ereignis::Groesse { cols, rows } => {
|
||||
screen.resize(cols, rows);
|
||||
model.resize(cols, rows);
|
||||
}
|
||||
event @ (Ereignis::Abbruch | Ereignis::Ende | Ereignis::Signal(_)) => {
|
||||
queued.push_front(event);
|
||||
queued.push_front(event.into());
|
||||
return None;
|
||||
}
|
||||
}
|
||||
@@ -2435,7 +2478,7 @@ fn dialog_event(
|
||||
pub fn msgbox_dialog(
|
||||
screen: &mut TextScreen,
|
||||
host: &mut dyn Host,
|
||||
queued: &mut VecDeque<Ereignis>,
|
||||
queued: &mut VecDeque<tb_runtime::builtins::Eingabe>,
|
||||
text: &str,
|
||||
kind: i32,
|
||||
title: &str,
|
||||
@@ -2549,7 +2592,7 @@ pub fn msgbox_dialog(
|
||||
pub fn inputbox_dialog(
|
||||
screen: &mut TextScreen,
|
||||
host: &mut dyn Host,
|
||||
queued: &mut VecDeque<Ereignis>,
|
||||
queued: &mut VecDeque<tb_runtime::builtins::Eingabe>,
|
||||
prompt: &str,
|
||||
title: &str,
|
||||
initial: &str,
|
||||
|
||||
@@ -86,7 +86,33 @@ impl Host for TerminalHost {
|
||||
self.start.elapsed().as_millis() as u64
|
||||
}
|
||||
|
||||
fn warten(&mut self, deadline_ms: Option<u64>) -> Option<Ereignis> {
|
||||
loop {
|
||||
if let Some(e) = self.next_event(false) {
|
||||
return Some(e);
|
||||
}
|
||||
let timeout = match deadline_ms {
|
||||
Some(deadline) => {
|
||||
let rest = deadline.saturating_sub(self.jetzt_ms());
|
||||
if rest == 0 {
|
||||
return None;
|
||||
}
|
||||
Duration::from_millis(rest.min(10))
|
||||
}
|
||||
None => Duration::from_millis(10),
|
||||
};
|
||||
// Begrenzt warten, damit auch SIGINT/SIGTERM ohne Terminaltaste
|
||||
// abgeholt werden. Die VM kennt dieses Backend-Intervall nicht.
|
||||
if event::poll(timeout).is_err() {
|
||||
return Some(Ereignis::Ende);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn next_event(&mut self, blockierend: bool) -> Option<Ereignis> {
|
||||
if blockierend {
|
||||
return self.warten(None);
|
||||
}
|
||||
loop {
|
||||
// Signale vor dem Terminal: sie liegen schon an, wenn wir hier
|
||||
// ankommen, und ein blockierendes `read` wuerde sie liegenlassen.
|
||||
@@ -181,7 +207,7 @@ fn taste_zu_ereignis(k: KeyEvent) -> Option<Ereignis> {
|
||||
KeyCode::Enter => taste::ENTER.to_string(),
|
||||
KeyCode::Backspace => taste::BACKSPACE.to_string(),
|
||||
KeyCode::Esc => taste::ESC.to_string(),
|
||||
KeyCode::Tab => taste::TAB.to_string(),
|
||||
KeyCode::Tab | KeyCode::BackTab => taste::TAB.to_string(),
|
||||
// Sondertasten in der Kodierung des Vorbilds: Nullzeichen + Kennung.
|
||||
KeyCode::F(n @ 1..=10) => taste::sonder(58 + n),
|
||||
KeyCode::Home => taste::sonder(71),
|
||||
@@ -197,7 +223,7 @@ fn taste_zu_ereignis(k: KeyEvent) -> Option<Ereignis> {
|
||||
_ => return None,
|
||||
};
|
||||
let mut shift = 0u8;
|
||||
if k.modifiers.contains(KeyModifiers::SHIFT) {
|
||||
if k.modifiers.contains(KeyModifiers::SHIFT) || k.code == KeyCode::BackTab {
|
||||
shift |= umschalt::SHIFT;
|
||||
}
|
||||
if k.modifiers.contains(KeyModifiers::CONTROL) {
|
||||
@@ -213,6 +239,16 @@ fn taste_zu_ereignis(k: KeyEvent) -> Option<Ereignis> {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn backtab_ist_tab_mit_shift_auch_ohne_modifier() {
|
||||
for modifiers in [KeyModifiers::NONE, KeyModifiers::SHIFT, KeyModifiers::ALT] {
|
||||
assert_eq!(
|
||||
taste_zu_ereignis(KeyEvent::new(KeyCode::BackTab, modifiers)),
|
||||
taste_zu_ereignis(KeyEvent::new(KeyCode::Tab, modifiers | KeyModifiers::SHIFT))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zeichentaste_wird_zu_inkey_form() {
|
||||
let k = KeyEvent::new(KeyCode::Char('a'), KeyModifiers::NONE);
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
//! Bildschirm-Runtime und Forms-Engine von Terminal Basic, aufbauend auf Ratatui.
|
||||
//! Terminalunabhängiges Forms-Modell, FRM-Reader und Dialoge.
|
||||
//!
|
||||
//! Zwei Schichten:
|
||||
//! 1. `screen`: Emulation des 80×25-Textbildschirms (Unicode/UTF-8, 16 Farben,
|
||||
//! Cursor) als Zeichenpuffer, gerendert über Ratatui. Darauf setzen die
|
||||
//! klassischen Anweisungen `PRINT`, `LOCATE`, `COLOR`, `CLS`, `INPUT` auf.
|
||||
//! 2. `forms`: ereignisgesteuerte Forms-Engine — Fenster, Steuerelemente
|
||||
//! (Schaltflächen, Textfelder, Listen …), Menüs, Fokusreihenfolge,
|
||||
//! Tastatur- und Mausereignisse.
|
||||
//! Die Ausführung verwendet `tb_runtime::host::Host` und dessen Zellenpuffer.
|
||||
//! Das optionale Feature `terminal` ergänzt Anzeige über Ratatui,
|
||||
//! crossterm-Eingaben und Betriebssystemsignale.
|
||||
|
||||
pub mod events;
|
||||
pub mod forms;
|
||||
pub mod frm;
|
||||
mod frm_pcode;
|
||||
#[cfg(feature = "terminal")]
|
||||
pub mod host; // Terminal-Host: Anzeige + Tastatur-/Größenereignisse
|
||||
#[cfg(feature = "terminal")]
|
||||
pub mod screen;
|
||||
#[cfg(feature = "terminal")]
|
||||
pub mod signale; // Betriebssystemsignale als Ereignisquelle (SIGNAL)
|
||||
|
||||
Reference in New Issue
Block a user