///|
pub(all) enum InputType {
TextInput
NumberInput
DigitInput
IdCardInput
} derive(Eq, Debug)
///|
fn InputType::host_name(self : InputType) -> String {
match self {
TextInput => "text"
NumberInput => "number"
DigitInput => "digit"
IdCardInput => "idcard"
}
}
///|
pub(all) enum ImageMode {
ScaleToFill
AspectFit
AspectFill
WidthFix
HeightFix
ImageCenter
} derive(Eq, Debug)
///|
fn ImageMode::host_name(self : ImageMode) -> String {
match self {
ScaleToFill => "scaleToFill"
AspectFit => "aspectFit"
AspectFill => "aspectFill"
WidthFix => "widthFix"
HeightFix => "heightFix"
ImageCenter => "center"
}
}
///|
pub struct ImageLoadDetail {
width : Double
height : Double
} derive(Eq, Debug)
///|
fn finite_number(value : Double) -> Bool {
value == value && value - value == 0.0
}
///|
fn finite_detail_number(
fields : Map[String, Json],
key : String,
) -> Result[Double, DecodeError] {
match fields.get(key) {
Some(Number(value, ..)) if finite_number(value) => Ok(value)
_ => Err(decode_error("event " + key + " must be a finite number"))
}
}
///|
fn detail_image_load(payload : Json) -> Result[ImageLoadDetail, DecodeError] {
let fields = match event_detail(payload) {
Ok(value) => value
Err(error) => return Err(error)
}
let width = match finite_detail_number(fields, "width") {
Ok(value) => value
Err(error) => return Err(error)
}
let height = match finite_detail_number(fields, "height") {
Ok(value) => value
Err(error) => return Err(error)
}
if width < 0.0 || height < 0.0 {
Err(decode_error("image dimensions must not be negative"))
} else {
Ok({ width, height, })
}
}
///|
fn detail_image_error(payload : Json) -> Result[String, DecodeError] {
let fields = match event_detail(payload) {
Ok(value) => value
Err(error) => return Err(error)
}
match fields.get("errMsg") {
Some(String(message)) => Ok(message)
_ => Err(decode_error("image error.errMsg must be a string"))
}
}
///|
pub(all) enum FormButtonType {
SubmitForm
ResetForm
} derive(Eq, Debug)
///|
/// Native button with rich children. The existing string-label button is unchanged.
pub fn[C : IsChildren] button_children(
on_tap~ : Cmd,
event_key? : String = "",
disabled? : Bool = false,
form_type? : FormButtonType,
name? : String = "",
id? : String = "",
class? : String = "",
style? : String = "",
data_section? : String = "",
semantics? : Semantics,
children : C,
) -> Node {
let binding = event_binding(
@renderer.page_event_kind("tap"),
_ => Ok(on_tap),
explicit_key=event_key,
)
let children = children.to_nodes()
let attrs = raw_element_attrs(style, data_section, semantics?)
raw_bool_attr(attrs, "disabled", disabled)
raw_string_attr(attrs, "name", name)
if form_type is Some(value) {
raw_string_attr(
attrs,
"form-type",
match value {
SubmitForm => "submit"
ResetForm => "reset"
},
)
}
raw_event_attr(attrs, "on_click", "tap", Some(binding))
let bindings = child_bindings(children)
append_direct_binding(bindings, binding)
wrap_node(
@ui.element("button", id~, class~, attrs~, children=child_raw(children)),
bindings,
)
}
///|
pub fn[C : IsChildren] label(
for_id~ : String,
id? : String = "",
class? : String = "",
style? : String = "",
children : C,
) -> Node {
let children = children.to_nodes()
let attrs = raw_element_attrs(style, "")
raw_string_attr(attrs, "for", for_id)
wrap_node(
@ui.element("label", id~, class~, attrs~, children=child_raw(children)),
child_bindings(children),
)
}
///|
pub fn[C : IsChildren] form(
on_submit~ : Emit[Map[String, Json]],
on_reset? : Cmd,
submit_key? : String = "",
reset_key? : String = "",
id? : String = "",
class? : String = "",
style? : String = "",
children : C,
) -> Node {
let children = children.to_nodes()
let attrs = raw_element_attrs(style, "")
let submit = event_binding(
@renderer.page_event_kind("submit"),
payload => {
let fields = match event_detail(payload) {
Ok(value) => value
Err(error) => return Err(error)
}
match fields.get("value") {
Some(Object(value)) => Ok(on_submit(value))
_ => Err(decode_error("form detail.value must be an object"))
}
},
explicit_key=submit_key,
)
let reset = on_reset.map(command => {
event_binding(
@renderer.page_event_kind("reset"),
_ => Ok(command),
explicit_key=reset_key,
)
})
let bindings = child_bindings(children)
append_direct_binding(bindings, submit)
if reset is Some(binding) {
append_direct_binding(bindings, binding)
}
raw_event_attr(attrs, "on_submit", "submit", Some(submit))
raw_event_attr(attrs, "on_reset", "reset", reset)
wrap_node(
@ui.element("form", id~, class~, attrs~, children=child_raw(children)),
bindings,
)
}
///|
fn detail_slider(payload : Json) -> Result[Int, DecodeError] {
let fields = match event_detail(payload) {
Ok(value) => value
Err(error) => return Err(error)
}
let value = match finite_detail_number(fields, "value") {
Ok(value) => value
Err(error) => return Err(error)
}
if value < -2147483648.0 ||
value > 2147483647.0 ||
value.to_int().to_double() != value {
Err(decode_error("slider value must be an integer"))
} else {
Ok(value.to_int())
}
}
///|
pub fn slider(
value~ : Int,
on_change~ : Emit[Int],
on_changing? : Emit[Int],
min? : Int = 0,
max? : Int = 100,
step? : Int = 1,
disabled? : Bool = false,
show_value? : Bool = false,
active_color? : String = "",
background_color? : String = "",
block_color? : String = "",
block_size? : Int = 28,
name? : String = "",
event_key? : String = "",
changing_key? : String = "",
id? : String = "",
class? : String = "",
style? : String = "",
semantics? : Semantics,
) -> Node {
guard min < max &&
step > 0 &&
(max.to_int64() - min.to_int64()) % step.to_int64() == 0L else {
abort("slider requires min < max and a positive step dividing the range")
}
guard value >= min && value <= max && block_size >= 12 && block_size <= 28 else {
abort("slider value or block size is out of range")
}
let attrs = raw_element_attrs(style, "", semantics?)
for
pair in [
("value-index", value),
("min", min),
("max", max),
("step", step),
("block-size", block_size),
] {
raw_int_attr(attrs, pair.0, pair.1)
}
raw_bool_attr(attrs, "disabled", disabled)
raw_bool_attr(attrs, "show-value", show_value)
for
pair in [
("active-color", active_color),
("background-color", background_color),
("block-color", block_color),
("name", name),
] {
raw_string_attr(attrs, pair.0, pair.1)
}
let change = event_binding(
@renderer.page_event_kind("change"),
payload => detail_slider(payload).map(value => on_change(value)),
explicit_key=event_key,
)
let changing = on_changing.map(send => {
event_binding(
@renderer.page_event_kind("changing"),
payload => detail_slider(payload).map(value => send(value)),
explicit_key=changing_key,
)
})
let bindings = [DirectBinding(change)]
if changing is Some(binding) {
append_direct_binding(bindings, binding)
}
raw_event_attr(attrs, "on_change", "change", Some(change))
raw_event_attr(attrs, "on_changing", "changing", changing)
wrap_node(@ui.element("slider", id~, class~, attrs~), bindings)
}
///|
pub fn progress(
percent~ : Int,
stroke_width? : Int = 6,
show_info? : Bool = false,
active? : Bool = false,
active_color? : String = "",
background_color? : String = "",
id? : String = "",
class? : String = "",
style? : String = "",
semantics? : Semantics,
) -> Node {
guard percent >= 0 && percent <= 100 && stroke_width > 0 else {
abort("progress requires a percentage in 0..100 and positive stroke width")
}
let attrs = raw_element_attrs(style, "", semantics?)
raw_int_attr(attrs, "percent", percent)
raw_int_attr(attrs, "stroke-width", stroke_width)
raw_bool_attr(attrs, "show-info", show_info)
raw_bool_attr(attrs, "active", active)
raw_string_attr(attrs, "active-color", active_color)
raw_string_attr(attrs, "background-color", background_color)
wrap_node(@ui.element("progress", id~, class~, attrs~), [])
}
///|
pub struct TouchPoint {
identifier : Int
client_x : Double
client_y : Double
page_x : Double
page_y : Double
} derive(Eq, Debug)
///|
pub struct TouchDetail {
touches : Array[TouchPoint]
changed_touches : Array[TouchPoint]
timestamp : Double
} derive(Eq, Debug)
///|
fn touch_point(value : Json) -> Result[TouchPoint, DecodeError] {
guard value is Object(fields) else {
return Err(decode_error("touch point must be an object"))
}
let identifier = match finite_detail_number(fields, "identifier") {
Ok(value) =>
match event_int32(value) {
Some(value) => value
None =>
return Err(
decode_error("touch identifier must be a nonnegative integer"),
)
}
Err(error) => return Err(error)
}
let client_x = match finite_detail_number(fields, "clientX") {
Ok(value) => value
Err(error) => return Err(error)
}
let client_y = match finite_detail_number(fields, "clientY") {
Ok(value) => value
Err(error) => return Err(error)
}
let page_x = match finite_detail_number(fields, "pageX") {
Ok(value) => value
Err(error) => return Err(error)
}
let page_y = match finite_detail_number(fields, "pageY") {
Ok(value) => value
Err(error) => return Err(error)
}
Ok({ identifier, client_x, client_y, page_x, page_y, })
}
///|
fn touch_points(
root : Map[String, Json],
name : String,
) -> Result[Array[TouchPoint], DecodeError] {
guard root.get(name) is Some(Array(values)) else {
return Err(decode_error(name + " must be an array"))
}
let points : Array[TouchPoint] = []
let ids : Set[Int] = Set([])
for value in values {
match touch_point(value) {
Ok(point) => {
if ids.contains(point.identifier) {
return Err(decode_error("duplicate touch identifier"))
}
ids.add(point.identifier)
points.push(point)
}
Err(error) => return Err(error)
}
}
Ok(points)
}
///|
fn detail_touch(payload : Json) -> Result[TouchDetail, DecodeError] {
guard payload is Object(root) else {
return Err(decode_error("touch event must be an object"))
}
let timestamp = match finite_detail_number(root, "timeStamp") {
Ok(value) if value >= 0.0 => value
_ => return Err(decode_error("touch timestamp must be nonnegative"))
}
let touches = match touch_points(root, "touches") {
Ok(value) => value
Err(error) => return Err(error)
}
touch_points(root, "changedTouches").map(changed_touches => {
touches,
changed_touches,
timestamp,
})
}
///|
pub fn[C : IsChildren] touch_view(
on_tap? : Cmd,
on_long_press? : Emit[TouchDetail],
on_touch_start? : Emit[TouchDetail],
on_touch_move? : Emit[TouchDetail],
on_touch_end? : Emit[TouchDetail],
on_touch_cancel? : Emit[TouchDetail],
catch_move? : Bool = false,
event_key? : String = "",
id? : String = "",
class? : String = "",
style? : String = "",
data_section? : String = "",
semantics? : Semantics,
children : C,
) -> Node {
let children = children.to_nodes()
let attrs = raw_element_attrs(style, data_section, semantics?)
let bindings = child_bindings(children)
raw_bool_attr(attrs, "catch-move", catch_move)
if on_tap is Some(command) {
let binding = event_binding(
@renderer.page_event_kind("tap"),
_ => Ok(command),
explicit_key=if event_key == "" { "" } else { event_key + "/tap" },
)
raw_event_attr(attrs, "on_click", "tap", Some(binding))
append_direct_binding(bindings, binding)
}
for
pair in [
("longpress", on_long_press),
("touchstart", on_touch_start),
("touchmove", on_touch_move),
("touchend", on_touch_end),
("touchcancel", on_touch_cancel),
] {
if pair.1 is Some(send) {
let kind = pair.0
let binding = event_binding(
@renderer.page_event_kind(kind),
payload => detail_touch(payload).map(value => send(value)),
explicit_key=if event_key == "" { "" } else { event_key + "/" + kind },
)
raw_event_attr(attrs, "on_" + kind, kind, Some(binding))
append_direct_binding(bindings, binding)
}
}
wrap_node(
@ui.element("view", id~, class~, attrs~, children=child_raw(children)),
bindings,
)
}