Phase 4: Formularmodell und Objektsprache
This commit is contained in:
631
crates/tb-frontend/src/forms.rs
Normal file
631
crates/tb-frontend/src/forms.rs
Normal file
@@ -0,0 +1,631 @@
|
||||
//! Statische Forms-Typinformation. Namen werden beim Übersetzen in diese
|
||||
//! Tabellenindizes aufgelöst; die Laufzeit muss keine Strings durchsuchen.
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum ObjectClass {
|
||||
Form,
|
||||
CheckBox,
|
||||
ComboBox,
|
||||
CommandButton,
|
||||
DirListBox,
|
||||
DriveListBox,
|
||||
FileListBox,
|
||||
Frame,
|
||||
HScrollBar,
|
||||
Label,
|
||||
ListBox,
|
||||
Menu,
|
||||
OptionButton,
|
||||
PictureBox,
|
||||
TextBox,
|
||||
Timer,
|
||||
VScrollBar,
|
||||
Screen,
|
||||
}
|
||||
|
||||
impl ObjectClass {
|
||||
pub const ALL: [Self; 18] = [
|
||||
Self::Form,
|
||||
Self::CheckBox,
|
||||
Self::ComboBox,
|
||||
Self::CommandButton,
|
||||
Self::DirListBox,
|
||||
Self::DriveListBox,
|
||||
Self::FileListBox,
|
||||
Self::Frame,
|
||||
Self::HScrollBar,
|
||||
Self::Label,
|
||||
Self::ListBox,
|
||||
Self::Menu,
|
||||
Self::OptionButton,
|
||||
Self::PictureBox,
|
||||
Self::TextBox,
|
||||
Self::Timer,
|
||||
Self::VScrollBar,
|
||||
Self::Screen,
|
||||
];
|
||||
|
||||
pub fn name(self) -> &'static str {
|
||||
match self {
|
||||
Self::Form => "FORM",
|
||||
Self::CheckBox => "CHECKBOX",
|
||||
Self::ComboBox => "COMBOBOX",
|
||||
Self::CommandButton => "COMMANDBUTTON",
|
||||
Self::DirListBox => "DIRLISTBOX",
|
||||
Self::DriveListBox => "DRIVELISTBOX",
|
||||
Self::FileListBox => "FILELISTBOX",
|
||||
Self::Frame => "FRAME",
|
||||
Self::HScrollBar => "HSCROLLBAR",
|
||||
Self::Label => "LABEL",
|
||||
Self::ListBox => "LISTBOX",
|
||||
Self::Menu => "MENU",
|
||||
Self::OptionButton => "OPTIONBUTTON",
|
||||
Self::PictureBox => "PICTUREBOX",
|
||||
Self::TextBox => "TEXTBOX",
|
||||
Self::Timer => "TIMER",
|
||||
Self::VScrollBar => "VSCROLLBAR",
|
||||
Self::Screen => "SCREEN",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse(name: &str) -> Option<Self> {
|
||||
Self::ALL
|
||||
.into_iter()
|
||||
.find(|c| c.name().eq_ignore_ascii_case(name))
|
||||
}
|
||||
|
||||
pub fn id(self) -> u8 {
|
||||
Self::ALL.iter().position(|c| *c == self).unwrap() as u8
|
||||
}
|
||||
|
||||
pub fn from_id(id: u8) -> Option<Self> {
|
||||
Self::ALL.get(id as usize).copied()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum PropertyType {
|
||||
Integer,
|
||||
Single,
|
||||
String,
|
||||
Boolean,
|
||||
Object,
|
||||
IntegerArray,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum PropertyDefault {
|
||||
Integer(i32),
|
||||
Single(f32),
|
||||
String(&'static str),
|
||||
Boolean(bool),
|
||||
Empty,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct PropertySpec {
|
||||
pub name: &'static str,
|
||||
pub ty: PropertyType,
|
||||
pub default: PropertyDefault,
|
||||
pub min: Option<i32>,
|
||||
pub max: Option<i32>,
|
||||
pub writable: bool,
|
||||
}
|
||||
|
||||
const fn int(name: &'static str, default: i32) -> PropertySpec {
|
||||
PropertySpec {
|
||||
name,
|
||||
ty: PropertyType::Integer,
|
||||
default: PropertyDefault::Integer(default),
|
||||
min: None,
|
||||
max: None,
|
||||
writable: true,
|
||||
}
|
||||
}
|
||||
const fn range(name: &'static str, default: i32, min: i32, max: i32) -> PropertySpec {
|
||||
PropertySpec {
|
||||
name,
|
||||
ty: PropertyType::Integer,
|
||||
default: PropertyDefault::Integer(default),
|
||||
min: Some(min),
|
||||
max: Some(max),
|
||||
writable: true,
|
||||
}
|
||||
}
|
||||
const fn boolp(name: &'static str, default: bool) -> PropertySpec {
|
||||
PropertySpec {
|
||||
name,
|
||||
ty: PropertyType::Boolean,
|
||||
default: PropertyDefault::Boolean(default),
|
||||
min: None,
|
||||
max: None,
|
||||
writable: true,
|
||||
}
|
||||
}
|
||||
const fn string(name: &'static str, default: &'static str) -> PropertySpec {
|
||||
PropertySpec {
|
||||
name,
|
||||
ty: PropertyType::String,
|
||||
default: PropertyDefault::String(default),
|
||||
min: None,
|
||||
max: None,
|
||||
writable: true,
|
||||
}
|
||||
}
|
||||
const fn ro(name: &'static str, ty: PropertyType) -> PropertySpec {
|
||||
PropertySpec {
|
||||
name,
|
||||
ty,
|
||||
default: PropertyDefault::Empty,
|
||||
min: None,
|
||||
max: None,
|
||||
writable: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Eigenschaftstabelle in stabiler Reihenfolge. Der Vektor ist klein und wird
|
||||
/// nur beim Übersetzen bzw. Erzeugen eines Objekts aufgebaut.
|
||||
pub fn properties(class: ObjectClass) -> Vec<PropertySpec> {
|
||||
use ObjectClass::*;
|
||||
let mut p = Vec::new();
|
||||
if !matches!(class, Timer | Menu | Screen) {
|
||||
p.extend([
|
||||
range("BACKCOLOR", 7, 0, 15),
|
||||
boolp("ENABLED", true),
|
||||
range("HEIGHT", 1, 1, 254),
|
||||
range("LEFT", 0, 0, 254),
|
||||
range("MOUSEPOINTER", 0, 0, 12),
|
||||
ro("PARENT", PropertyType::Object),
|
||||
string("TAG", ""),
|
||||
range("TOP", 0, 0, 254),
|
||||
boolp("VISIBLE", true),
|
||||
range("WIDTH", 1, 1, 254),
|
||||
]);
|
||||
}
|
||||
if !matches!(
|
||||
class,
|
||||
Form | Frame | Label | PictureBox | Timer | Menu | Screen
|
||||
) {
|
||||
p.extend([int("INDEX", 0), int("TABINDEX", 0), boolp("TABSTOP", true)]);
|
||||
}
|
||||
if !matches!(class, Form | Timer | Menu | Screen) {
|
||||
p.push(string("CTLNAME", ""));
|
||||
}
|
||||
if matches!(
|
||||
class,
|
||||
Form | TextBox
|
||||
| ListBox
|
||||
| ComboBox
|
||||
| CheckBox
|
||||
| OptionButton
|
||||
| Label
|
||||
| HScrollBar
|
||||
| VScrollBar
|
||||
| PictureBox
|
||||
) {
|
||||
p.push(range("FORECOLOR", 0, 0, 15));
|
||||
}
|
||||
if matches!(
|
||||
class,
|
||||
Form | CommandButton
|
||||
| TextBox
|
||||
| ListBox
|
||||
| ComboBox
|
||||
| CheckBox
|
||||
| OptionButton
|
||||
| Frame
|
||||
| Label
|
||||
| HScrollBar
|
||||
| VScrollBar
|
||||
| PictureBox
|
||||
) {
|
||||
p.push(range("DRAGMODE", 0, 0, 1));
|
||||
}
|
||||
match class {
|
||||
Form => p.extend([
|
||||
boolp("AUTOREDRAW", false),
|
||||
range("BORDERSTYLE", 2, 0, 6),
|
||||
string("CAPTION", ""),
|
||||
boolp("CONTROLBOX", true),
|
||||
int("CURRENTX", 0),
|
||||
int("CURRENTY", 0),
|
||||
string("FORMNAME", ""),
|
||||
range("FORMTYPE", 0, 0, 1),
|
||||
boolp("MAXBUTTON", true),
|
||||
boolp("MINBUTTON", true),
|
||||
ro("SCALEHEIGHT", PropertyType::Integer),
|
||||
ro("SCALEWIDTH", PropertyType::Integer),
|
||||
range("WINDOWSTATE", 0, 0, 2),
|
||||
]),
|
||||
CommandButton => p.extend([
|
||||
boolp("CANCEL", false),
|
||||
string("CAPTION", ""),
|
||||
boolp("DEFAULT", false),
|
||||
boolp("VALUE", false),
|
||||
]),
|
||||
TextBox => p.extend([
|
||||
range("BORDERSTYLE", 1, 0, 1),
|
||||
boolp("MULTILINE", false),
|
||||
range("SCROLLBARS", 0, 0, 3),
|
||||
int("SELLENGTH", 0),
|
||||
int("SELSTART", 0),
|
||||
string("SELTEXT", ""),
|
||||
string("TEXT", ""),
|
||||
]),
|
||||
ListBox => p.extend([
|
||||
ro("LIST", PropertyType::String),
|
||||
ro("LISTCOUNT", PropertyType::Integer),
|
||||
int("LISTINDEX", -1),
|
||||
boolp("SORTED", false),
|
||||
string("TEXT", ""),
|
||||
]),
|
||||
ComboBox => p.extend([
|
||||
ro("LIST", PropertyType::String),
|
||||
ro("LISTCOUNT", PropertyType::Integer),
|
||||
int("LISTINDEX", -1),
|
||||
boolp("SORTED", false),
|
||||
int("SELLENGTH", 0),
|
||||
int("SELSTART", 0),
|
||||
string("SELTEXT", ""),
|
||||
range("STYLE", 0, 0, 2),
|
||||
string("TEXT", ""),
|
||||
]),
|
||||
CheckBox => p.extend([string("CAPTION", ""), range("VALUE", 0, 0, 2)]),
|
||||
OptionButton => p.extend([string("CAPTION", ""), range("VALUE", 0, -1, 0)]),
|
||||
Frame => p.push(string("CAPTION", "")),
|
||||
Label => p.extend([
|
||||
range("ALIGNMENT", 0, 0, 2),
|
||||
boolp("AUTOSIZE", false),
|
||||
range("BORDERSTYLE", 0, 0, 2),
|
||||
string("CAPTION", ""),
|
||||
]),
|
||||
HScrollBar | VScrollBar => p.extend([
|
||||
ro("ATTACHED", PropertyType::Boolean),
|
||||
range("LARGECHANGE", 1, 1, 32767),
|
||||
range("MIN", 0, -32768, 32767),
|
||||
range("MAX", 32767, -32768, 32767),
|
||||
range("SMALLCHANGE", 1, 1, 32767),
|
||||
range("VALUE", 0, -32768, 32767),
|
||||
]),
|
||||
PictureBox => p.extend([
|
||||
boolp("AUTOREDRAW", false),
|
||||
range("BORDERSTYLE", 1, 0, 2),
|
||||
int("CURRENTX", 0),
|
||||
int("CURRENTY", 0),
|
||||
ro("SCALEHEIGHT", PropertyType::Integer),
|
||||
ro("SCALEWIDTH", PropertyType::Integer),
|
||||
]),
|
||||
Timer => p.extend([
|
||||
string("CTLNAME", ""),
|
||||
boolp("ENABLED", true),
|
||||
int("INDEX", 0),
|
||||
range("INTERVAL", 0, 0, 65535),
|
||||
ro("PARENT", PropertyType::Object),
|
||||
string("TAG", ""),
|
||||
]),
|
||||
Menu => p.extend([
|
||||
string("CAPTION", ""),
|
||||
boolp("CHECKED", false),
|
||||
string("CTLNAME", ""),
|
||||
boolp("ENABLED", true),
|
||||
int("INDEX", 0),
|
||||
ro("PARENT", PropertyType::Object),
|
||||
boolp("SEPARATOR", false),
|
||||
string("TAG", ""),
|
||||
boolp("VISIBLE", true),
|
||||
]),
|
||||
DirListBox => p.extend([string("PATH", ""), string("TEXT", "")]),
|
||||
DriveListBox => p.extend([string("DRIVE", ""), string("TEXT", "")]),
|
||||
FileListBox => p.extend([
|
||||
string("FILENAME", ""),
|
||||
string("PATH", ""),
|
||||
string("PATTERN", "*.*"),
|
||||
boolp("ARCHIVE", true),
|
||||
boolp("HIDDEN", false),
|
||||
boolp("NORMAL", true),
|
||||
boolp("READONLY", true),
|
||||
boolp("SYSTEM", false),
|
||||
string("TEXT", ""),
|
||||
]),
|
||||
Screen => p.extend([
|
||||
ro("ACTIVECONTROL", PropertyType::Object),
|
||||
ro("ACTIVEFORM", PropertyType::Object),
|
||||
boolp("AUTOREDRAW", false),
|
||||
PropertySpec {
|
||||
name: "CONTROLPANEL",
|
||||
ty: PropertyType::IntegerArray,
|
||||
default: PropertyDefault::Integer(0),
|
||||
min: Some(0),
|
||||
max: Some(15),
|
||||
writable: true,
|
||||
},
|
||||
ro("HEIGHT", PropertyType::Integer),
|
||||
ro("WIDTH", PropertyType::Integer),
|
||||
range("MOUSEPOINTER", 0, 0, 12),
|
||||
]),
|
||||
}
|
||||
if class == Form {
|
||||
if let Some(visible) = p.iter_mut().find(|p| p.name == "VISIBLE") {
|
||||
visible.default = PropertyDefault::Boolean(false);
|
||||
}
|
||||
}
|
||||
p
|
||||
}
|
||||
|
||||
pub fn property(class: ObjectClass, name: &str) -> Option<(u16, PropertySpec)> {
|
||||
properties(class)
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.find(|(_, p)| p.name.eq_ignore_ascii_case(name))
|
||||
.map(|(i, p)| (i as u16, p))
|
||||
}
|
||||
|
||||
pub fn methods(class: ObjectClass) -> &'static [&'static str] {
|
||||
use ObjectClass::*;
|
||||
match class {
|
||||
Form => &[
|
||||
"CLS",
|
||||
"DRAG",
|
||||
"HIDE",
|
||||
"LOAD",
|
||||
"MOVE",
|
||||
"PRINT",
|
||||
"PRINTFORM",
|
||||
"REFRESH",
|
||||
"SHOW",
|
||||
"TEXTHEIGHT",
|
||||
"TEXTWIDTH",
|
||||
"UNLOAD",
|
||||
],
|
||||
ListBox | ComboBox => &[
|
||||
"ADDITEM",
|
||||
"DRAG",
|
||||
"MOVE",
|
||||
"REFRESH",
|
||||
"REMOVEITEM",
|
||||
"SETFOCUS",
|
||||
],
|
||||
CommandButton | TextBox | CheckBox | OptionButton => {
|
||||
&["DRAG", "MOVE", "REFRESH", "SETFOCUS"]
|
||||
}
|
||||
Frame | Label | HScrollBar | VScrollBar => &["DRAG", "MOVE", "REFRESH"],
|
||||
PictureBox => &[
|
||||
"CLS",
|
||||
"DRAG",
|
||||
"MOVE",
|
||||
"PRINT",
|
||||
"REFRESH",
|
||||
"SETFOCUS",
|
||||
"TEXTHEIGHT",
|
||||
"TEXTWIDTH",
|
||||
],
|
||||
Screen => &["HIDE", "SHOW"],
|
||||
_ => &[],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn method_is_implemented(class: ObjectClass, name: &str) -> bool {
|
||||
matches!(
|
||||
(class, name),
|
||||
(ObjectClass::Form, "HIDE" | "LOAD" | "SHOW" | "UNLOAD")
|
||||
| (ObjectClass::Screen, "HIDE" | "SHOW")
|
||||
)
|
||||
}
|
||||
|
||||
pub fn method_arity(class: ObjectClass, name: &str) -> Option<(usize, usize)> {
|
||||
match (class, name) {
|
||||
(ObjectClass::Form, "SHOW") => Some((0, 1)),
|
||||
(ObjectClass::Form, "HIDE" | "LOAD" | "UNLOAD")
|
||||
| (ObjectClass::Screen, "HIDE" | "SHOW") => Some((0, 0)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn events(class: ObjectClass) -> &'static [&'static str] {
|
||||
use ObjectClass::*;
|
||||
match class {
|
||||
Form => &[
|
||||
"CLICK",
|
||||
"DBLCLICK",
|
||||
"DRAGDROP",
|
||||
"DRAGOVER",
|
||||
"GOTFOCUS",
|
||||
"KEYDOWN",
|
||||
"KEYPRESS",
|
||||
"KEYUP",
|
||||
"LOAD",
|
||||
"LOSTFOCUS",
|
||||
"MOUSEDOWN",
|
||||
"MOUSEMOVE",
|
||||
"MOUSEUP",
|
||||
"PAINT",
|
||||
"RESIZE",
|
||||
"UNLOAD",
|
||||
],
|
||||
CommandButton => &[
|
||||
"CLICK",
|
||||
"DRAGDROP",
|
||||
"DRAGOVER",
|
||||
"GOTFOCUS",
|
||||
"KEYDOWN",
|
||||
"KEYPRESS",
|
||||
"KEYUP",
|
||||
"LOSTFOCUS",
|
||||
],
|
||||
TextBox => &[
|
||||
"CHANGE",
|
||||
"DRAGDROP",
|
||||
"DRAGOVER",
|
||||
"GOTFOCUS",
|
||||
"KEYDOWN",
|
||||
"KEYPRESS",
|
||||
"KEYUP",
|
||||
"LOSTFOCUS",
|
||||
],
|
||||
ListBox => &[
|
||||
"CLICK",
|
||||
"DBLCLICK",
|
||||
"DRAGDROP",
|
||||
"DRAGOVER",
|
||||
"GOTFOCUS",
|
||||
"KEYDOWN",
|
||||
"KEYPRESS",
|
||||
"KEYUP",
|
||||
"LOSTFOCUS",
|
||||
"MOUSEDOWN",
|
||||
"MOUSEMOVE",
|
||||
"MOUSEUP",
|
||||
],
|
||||
ComboBox => &[
|
||||
"CHANGE",
|
||||
"CLICK",
|
||||
"DBLCLICK",
|
||||
"DRAGDROP",
|
||||
"DRAGOVER",
|
||||
"DROPDOWN",
|
||||
"GOTFOCUS",
|
||||
"KEYDOWN",
|
||||
"KEYPRESS",
|
||||
"KEYUP",
|
||||
"LOSTFOCUS",
|
||||
"MOUSEDOWN",
|
||||
"MOUSEMOVE",
|
||||
"MOUSEUP",
|
||||
],
|
||||
CheckBox => &[
|
||||
"CLICK",
|
||||
"DRAGDROP",
|
||||
"DRAGOVER",
|
||||
"GOTFOCUS",
|
||||
"KEYDOWN",
|
||||
"KEYPRESS",
|
||||
"KEYUP",
|
||||
"LOSTFOCUS",
|
||||
],
|
||||
OptionButton => &[
|
||||
"CLICK",
|
||||
"DBLCLICK",
|
||||
"DRAGDROP",
|
||||
"DRAGOVER",
|
||||
"GOTFOCUS",
|
||||
"KEYDOWN",
|
||||
"KEYPRESS",
|
||||
"KEYUP",
|
||||
"LOSTFOCUS",
|
||||
],
|
||||
Frame => &["DRAGDROP", "DRAGOVER"],
|
||||
Label => &[
|
||||
"CHANGE",
|
||||
"CLICK",
|
||||
"DBLCLICK",
|
||||
"DRAGDROP",
|
||||
"DRAGOVER",
|
||||
"MOUSEDOWN",
|
||||
"MOUSEMOVE",
|
||||
"MOUSEUP",
|
||||
],
|
||||
HScrollBar | VScrollBar => &[
|
||||
"CHANGE",
|
||||
"DRAGDROP",
|
||||
"DRAGOVER",
|
||||
"GOTFOCUS",
|
||||
"KEYDOWN",
|
||||
"KEYPRESS",
|
||||
"KEYUP",
|
||||
"LOSTFOCUS",
|
||||
],
|
||||
PictureBox => &[
|
||||
"CLICK",
|
||||
"DBLCLICK",
|
||||
"DRAGDROP",
|
||||
"DRAGOVER",
|
||||
"GOTFOCUS",
|
||||
"KEYDOWN",
|
||||
"KEYPRESS",
|
||||
"KEYUP",
|
||||
"LOSTFOCUS",
|
||||
"MOUSEDOWN",
|
||||
"MOUSEMOVE",
|
||||
"MOUSEUP",
|
||||
"PAINT",
|
||||
],
|
||||
Timer => &["TIMER"],
|
||||
Menu => &["CLICK"],
|
||||
DirListBox => &["CHANGE", "CLICK", "DBLCLICK", "PATHCHANGE"],
|
||||
DriveListBox => &["CHANGE"],
|
||||
FileListBox => &["CLICK", "DBLCLICK", "PATTERNCHANGE", "PATHCHANGE"],
|
||||
Screen => &[],
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum EventParamType {
|
||||
Integer,
|
||||
Single,
|
||||
Control,
|
||||
}
|
||||
|
||||
pub fn event_params(event: &str) -> Option<&'static [(&'static str, EventParamType)]> {
|
||||
use EventParamType::*;
|
||||
Some(match event.to_ascii_uppercase().as_str() {
|
||||
"KEYDOWN" | "KEYUP" => &[("KEYCODE", Integer), ("SHIFT", Integer)],
|
||||
"KEYPRESS" => &[("KEYASCII", Integer)],
|
||||
"MOUSEDOWN" | "MOUSEMOVE" | "MOUSEUP" => &[
|
||||
("BUTTON", Integer),
|
||||
("SHIFT", Integer),
|
||||
("X", Single),
|
||||
("Y", Single),
|
||||
],
|
||||
"DRAGDROP" => &[("SOURCE", Control), ("X", Single), ("Y", Single)],
|
||||
"DRAGOVER" => &[
|
||||
("SOURCE", Control),
|
||||
("X", Single),
|
||||
("Y", Single),
|
||||
("STATE", Integer),
|
||||
],
|
||||
"UNLOAD" => &[("CANCEL", Integer)],
|
||||
"CLICK" | "DBLCLICK" | "CHANGE" | "DROPDOWN" | "GOTFOCUS" | "LOSTFOCUS" | "LOAD"
|
||||
| "PAINT" | "RESIZE" | "TIMER" | "PATHCHANGE" | "PATTERNCHANGE" => &[],
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FormObject {
|
||||
pub name: String,
|
||||
pub class: ObjectClass,
|
||||
pub parent_form: Option<String>,
|
||||
pub array: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct FormCatalog {
|
||||
pub objects: Vec<FormObject>,
|
||||
}
|
||||
|
||||
impl FormCatalog {
|
||||
pub fn add(
|
||||
&mut self,
|
||||
name: impl Into<String>,
|
||||
class: ObjectClass,
|
||||
parent_form: Option<&str>,
|
||||
array: bool,
|
||||
) -> u16 {
|
||||
let id = self.objects.len() as u16;
|
||||
self.objects.push(FormObject {
|
||||
name: name.into().to_uppercase(),
|
||||
class,
|
||||
parent_form: parent_form.map(str::to_uppercase),
|
||||
array,
|
||||
});
|
||||
id
|
||||
}
|
||||
|
||||
pub fn find(&self, name: &str) -> Option<(u16, &FormObject)> {
|
||||
self.objects
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_, o)| o.name.eq_ignore_ascii_case(name))
|
||||
.map(|(i, o)| (i as u16, o))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user