feat(ui): pointer events for natives and elements, with capture; on-down and on-up; hits in painting order
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
d2b0413c30
commit
0aad2294ec
20 changed files with 349 additions and 46 deletions
|
|
@ -1,8 +1,8 @@
|
|||
# attr_more.ludic - width and height (where 0 is 0 and calc() may hold a percentage), the flex
|
||||
# shorthand, flex-shrink, order and text-shadow
|
||||
# shorthand, flex-shrink, order, text-shadow and pointer-events
|
||||
function el_more(n: UiNode, key: string, v: Val) -> bool {
|
||||
let s = vl_text(v)
|
||||
if key == "shrink" { nx(n).shrink = Math.max(el_num(s), 0.0) } else if key == "order" { nx(n).order = int(el_num(s)) } else if key == "text-shadow" { el_text_shadow(n, s) } else { return false }
|
||||
if key == "shrink" { nx(n).shrink = Math.max(el_num(s), 0.0) } else if key == "order" { nx(n).order = int(el_num(s)) } else if key == "text-shadow" { el_text_shadow(n, s) } else if key == "pointer-events" { nx(n).no_pointer = tpl_words(s) == "none" } else { return false }
|
||||
return true
|
||||
}
|
||||
# width or height: a length (0 included), fill, a percentage, calc(), or auto/fit (none)
|
||||
|
|
|
|||
|
|
@ -42,11 +42,24 @@ function fc_keys(root: UiNode) -> void {
|
|||
if i.down_key or (i.right and not horizontal) { fc_move(1) }
|
||||
ct_num_blur(root)
|
||||
if n == null { return }
|
||||
fc_hold(root, n)
|
||||
if i.enter or (i.space and not ct_takes_space(n)) { ct_activate(n) }
|
||||
if horizontal and i.left { ct_step(n, -1) }
|
||||
if horizontal and i.right { ct_step(n, 1) }
|
||||
ct_typed(n)
|
||||
}
|
||||
# Enter or A held on a control: its on-down, and on-up when let go (or the focus moves away)
|
||||
function fc_hold(root: UiNode, n: UiNode) -> void {
|
||||
if fr_key_press != "" and (in_act_up or n.key != fr_key_press) {
|
||||
let was = fr_node(root, fr_key_press)
|
||||
if was != null { ui_fire(was, "up", Value.null()) }
|
||||
fr_key_press = ""
|
||||
}
|
||||
if in_act_down and n.enabled {
|
||||
fr_key_press = n.key
|
||||
ui_fire(n, "down", Value.null())
|
||||
}
|
||||
}
|
||||
export function ui_focus(key: string) -> void {
|
||||
fc_key = key
|
||||
fc_visible = false
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ import "media.ludic"
|
|||
import "select.ludic"
|
||||
import "select_parts.ludic"
|
||||
import "select_match.ludic"
|
||||
import "select_nth.ludic"
|
||||
import "cascade.ludic"
|
||||
import "cascade_merge.ludic"
|
||||
import "layout.ludic"
|
||||
|
|
@ -72,6 +73,8 @@ import "layout_run.ludic"
|
|||
import "layout_wrap.ludic"
|
||||
import "frame.ludic"
|
||||
import "pointer.ludic"
|
||||
import "pointer_pick.ludic"
|
||||
import "pointer_events.ludic"
|
||||
import "screen_class.ludic"
|
||||
import "draw.ludic"
|
||||
import "draw_content.ludic"
|
||||
|
|
|
|||
|
|
@ -24,9 +24,13 @@ export property UiInput {
|
|||
held_right: bool = false
|
||||
pad_a: bool = false # the pad's south button held: Enter
|
||||
pad_b: bool = false # its east button held: Escape
|
||||
held_enter: bool = false # Enter held (with pad_a: a button's on-down, then its on-up)
|
||||
down_right: bool = false # the other mouse buttons held
|
||||
down_middle: bool = false
|
||||
}
|
||||
var in_now: UiInput = new UiInput
|
||||
var in_was_down: bool = false
|
||||
var in_last: UiInput = new UiInput
|
||||
var in_given: bool = false
|
||||
function in_read() -> void {
|
||||
if in_given {
|
||||
|
|
@ -34,11 +38,14 @@ function in_read() -> void {
|
|||
return
|
||||
}
|
||||
in_was_down = in_now.down
|
||||
in_last = in_now
|
||||
let i = new UiInput
|
||||
if ui_be != null and ui_be.input {
|
||||
i.x = float(Input.mouse_x())
|
||||
i.y = float(Input.mouse_y())
|
||||
i.down = Input.mouse_down(0)
|
||||
i.down_right = Input.mouse_down(1)
|
||||
i.down_middle = Input.mouse_down(2)
|
||||
i.wheel = float(Input.wheel())
|
||||
i.tab = Input.key_pressed(Key.Tab)
|
||||
i.shift = Input.key_down(Key.Shift)
|
||||
|
|
@ -66,6 +73,7 @@ function in_any_key() -> int {
|
|||
# this frame's input, given by a test or a host that reads its own
|
||||
export function ui_input(i: UiInput) -> void {
|
||||
in_was_down = in_now.down
|
||||
in_last = in_now
|
||||
in_repeat(i)
|
||||
in_now = i
|
||||
in_given = true
|
||||
|
|
|
|||
|
|
@ -7,6 +7,9 @@ const PAD_STICK: float = 0.5
|
|||
var rp_next: []float = [-1.0, -1.0, -1.0, -1.0]
|
||||
var rp_a: bool = false
|
||||
var rp_b: bool = false
|
||||
var rp_act: bool = false
|
||||
var in_act_down: bool = false # A or Enter went down this frame, or came up
|
||||
var in_act_up: bool = false
|
||||
# what is held this frame: the arrow keys, the d-pad (SDL's order: 11 up, 12 down, 13 left, 14
|
||||
# right) and the stick past half way along its larger axis
|
||||
function in_held(i: UiInput) -> void {
|
||||
|
|
@ -14,6 +17,7 @@ function in_held(i: UiInput) -> void {
|
|||
i.held_down = Input.key_down(Key.Down)
|
||||
i.held_left = Input.key_down(Key.Left)
|
||||
i.held_right = Input.key_down(Key.Right)
|
||||
i.held_enter = Input.key_down(Key.Enter)
|
||||
if not Input.pad_connected(0) { return }
|
||||
let ax = float(Input.pad_axis(0, 0))
|
||||
let ay = float(Input.pad_axis(0, 1))
|
||||
|
|
@ -36,6 +40,10 @@ function in_repeat(i: UiInput) -> void {
|
|||
if i.pad_b and not rp_b { i.escape = true }
|
||||
rp_a = i.pad_a
|
||||
rp_b = i.pad_b
|
||||
let act = i.pad_a or i.held_enter
|
||||
in_act_down = act and not rp_act
|
||||
in_act_up = rp_act and not act
|
||||
rp_act = act
|
||||
}
|
||||
function rp_fire(k: int, held: bool, now: float) -> bool {
|
||||
if not held {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ export property UiNative {
|
|||
tag: string = ""
|
||||
measure: fn(UiNode, float) -> void = null # the content's size into n.mw, n.mh; avail is the room
|
||||
draw: fn(UiNode) -> void = null
|
||||
input: fn(UiNode, UiPointer) -> bool = null # the pointer on it (pointer_events.ludic)
|
||||
}
|
||||
var nt_list: []UiNative = new []UiNative
|
||||
export function ui_native(tag: string, measure: fn(UiNode, float) -> void, draw: fn(UiNode) -> void) -> void {
|
||||
|
|
@ -15,12 +16,23 @@ export function ui_native(tag: string, measure: fn(UiNode, float) -> void, draw:
|
|||
nv.draw = draw
|
||||
for i in 0 .. len(nt_list) {
|
||||
if nt_list[i].tag == tag {
|
||||
nv.input = nt_list[i].input
|
||||
nt_list[i] = nv
|
||||
return
|
||||
}
|
||||
}
|
||||
push(nt_list, nv)
|
||||
}
|
||||
# the pointer on a native: its presses, moves, drags, releases and the wheel, while it is over it or
|
||||
# has it captured; answering false to a pointerdown leaves the pointer free
|
||||
export function ui_native_input(tag: string, f: fn(UiNode, UiPointer) -> bool) -> void {
|
||||
let nv = nt_find(tag)
|
||||
if nv == null {
|
||||
ui_err(`ui_native_input: there is no native <{tag}> - ui_native it first`)
|
||||
return
|
||||
}
|
||||
nv.input = f
|
||||
}
|
||||
function nt_find(tag: string) -> UiNative {
|
||||
for i in 0 .. len(nt_list) {
|
||||
if nt_list[i].tag == tag { return nt_list[i] }
|
||||
|
|
@ -52,7 +64,8 @@ function nt_run(n: UiNode, name: string, value: Val) -> void {
|
|||
if n.on_names == null or not n.enabled { return }
|
||||
for i in 0 .. len(n.on_names) {
|
||||
if n.on_names[i] == name {
|
||||
let ev = Value.object()
|
||||
var ev = Value.object()
|
||||
if lk_in(PE_NAMES, name) and Value.kind(value) == 6 { ev = value }
|
||||
Value.put(ev, "name", Value.str(name))
|
||||
Value.put(ev, "value", value)
|
||||
let e = env_new(n.env)
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ export property UiNodeX {
|
|||
tsh_y: float = 0.0
|
||||
tsh_c: int = -2
|
||||
tsh_a: float = 1.0
|
||||
no_pointer: bool = false # pointer-events: none - the pointer goes through it
|
||||
}
|
||||
function nx(n: UiNode) -> UiNodeX {
|
||||
if n.ex == null { n.ex = new UiNodeX }
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
var fr_hover: []string = new []string
|
||||
var fr_press_key: string = ""
|
||||
var fr_down_now: bool = false
|
||||
var fr_key_press: string = "" # the control Enter or the pad's A is holding down
|
||||
function fr_was_hovered(key: string) -> bool { return sel_has(fr_hover, key) }
|
||||
function fr_pointer(root: UiNode) -> void {
|
||||
let i = in_now
|
||||
|
|
@ -18,40 +19,44 @@ function fr_pointer(root: UiNode) -> void {
|
|||
if top != null and pp_pointer(top) { return }
|
||||
if sc_drag != "" { return }
|
||||
let hit = fr_hit(scope, i.x, i.y)
|
||||
if i.down and not in_was_down {
|
||||
ct_key_pressed_away(hit)
|
||||
fr_press_key = ""
|
||||
if hit != null {
|
||||
fr_press_key = hit.key
|
||||
if hit.focusable { ui_focus(hit.key) }
|
||||
ct_press(hit, i.x, i.y)
|
||||
}
|
||||
}
|
||||
pe_frame(root, scope)
|
||||
if i.down and not in_was_down { fr_press(hit, i.x, i.y) }
|
||||
if i.down and fr_press_key != "" { ct_drag(root, fr_press_key, i.x, i.y) }
|
||||
if not i.down and in_was_down {
|
||||
let was = fr_node(root, fr_press_key)
|
||||
if was != null { ui_fire(was, "up", Value.null()) }
|
||||
if hit != null and hit.key == fr_press_key and hit.enabled { ct_activate(hit) }
|
||||
fr_press_key = ""
|
||||
}
|
||||
}
|
||||
function fr_press(hit: UiNode, x: float, y: float) -> void {
|
||||
ct_key_pressed_away(hit)
|
||||
fr_press_key = ""
|
||||
if hit == null { return }
|
||||
fr_press_key = hit.key
|
||||
if hit.focusable { ui_focus(hit.key) }
|
||||
if hit.enabled { ui_fire(hit, "down", Value.null()) }
|
||||
ct_press(hit, x, y)
|
||||
}
|
||||
# :active - the press began on it, or inside it, and is still held (by the pointer, Enter or A)
|
||||
function fr_active(n: UiNode) -> bool {
|
||||
if fr_down_now and fr_press_key != "" and lc_mine(fr_press_key, n.key) { return true }
|
||||
if pe_key != "" and lc_mine(pe_key, n.key) { return true }
|
||||
return fr_key_press != "" and lc_mine(fr_key_press, n.key)
|
||||
}
|
||||
function fr_under(n: UiNode, px: float, py: float, keys: []string) -> void {
|
||||
if px < n.x or py < n.y or px >= n.x + n.cw or py >= n.y + n.ch { return }
|
||||
if n.key != "" { push(keys, n.key) }
|
||||
for i in 0 .. len(n.children) { fr_under(n.children[i], px, py, keys) }
|
||||
}
|
||||
# the deepest control (or pressable element) under the pointer
|
||||
# the control (or pressable element) the pointer is on: out from the topmost element under it
|
||||
function fr_hit(n: UiNode, px: float, py: float) -> UiNode {
|
||||
if px < n.x or py < n.y or px >= n.x + n.cw or py >= n.y + n.ch { return null }
|
||||
var i = len(n.children) - 1
|
||||
while i >= 0 {
|
||||
let h = fr_hit(n.children[i], px, py)
|
||||
if h != null { return h }
|
||||
i -= 1
|
||||
}
|
||||
if n.focusable or n.act != null { return n }
|
||||
return null
|
||||
if not fr_in(n, px, py) { return null }
|
||||
return fr_pick(n, px, py, PK_CONTROL)
|
||||
}
|
||||
export function ui_hit(root: UiNode, px: float, py: float) -> UiNode { return fr_hit(root, px, py) }
|
||||
function fr_node(n: UiNode, key: string) -> UiNode {
|
||||
if key == "" { return null }
|
||||
if n.key == key { return n }
|
||||
for i in 0 .. len(n.children) {
|
||||
let f = fr_node(n.children[i], key)
|
||||
|
|
|
|||
85
packages/ludic.ui/pointer_events.ludic
Normal file
85
packages/ludic.ui/pointer_events.ludic
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
# pointer_events.ludic - on-pointerdown, on-pointermove, on-pointerup, on-wheel and on-drag on any
|
||||
# element, with event.x / event.y inside it, event.dx / event.dy, event.button and event.wheel; a
|
||||
# native takes the same through ui_native_input. A press captures the pointer until it is let go.
|
||||
export property UiPointer {
|
||||
kind: string = "" # pointerdown, pointermove, pointerup, wheel or drag
|
||||
x: float = 0.0 # inside the element
|
||||
y: float = 0.0
|
||||
dx: float = 0.0 # how far the pointer moved this frame
|
||||
dy: float = 0.0
|
||||
button: int = 0 # 0 the left button, 1 the right, 2 the middle
|
||||
wheel: float = 0.0
|
||||
}
|
||||
const PE_NAMES: string = " pointerdown pointermove pointerup wheel drag "
|
||||
var pe_key: string = "" # the element the pointer is captured by
|
||||
var pe_btn: int = 0
|
||||
function pe_wants(n: UiNode, wheel: bool) -> bool {
|
||||
if n.kind == UI_NATIVE and n.native != null and n.native.input != null { return true }
|
||||
if n.on_names == null { return false }
|
||||
for i in 0 .. len(n.on_names) {
|
||||
if wheel and n.on_names[i] == "wheel" { return true }
|
||||
if not wheel and lk_in(PE_NAMES, n.on_names[i]) { return true }
|
||||
}
|
||||
return false
|
||||
}
|
||||
# the pointer's events this frame: to what captured it, else to what it is over
|
||||
function pe_frame(root: UiNode, scope: UiNode) -> void {
|
||||
let i = in_now
|
||||
let p = in_last
|
||||
let moved = p.x >= 0.0 and (i.x != p.x or i.y != p.y)
|
||||
var over: UiNode = null
|
||||
if fr_in(scope, i.x, i.y) { over = fr_pick(scope, i.x, i.y, PK_POINTER) }
|
||||
if pe_key != "" {
|
||||
let c = fr_node(root, pe_key)
|
||||
if c != null and moved { pe_send(c, "pointermove", pe_btn) }
|
||||
if c != null and moved { pe_send(c, "drag", pe_btn) }
|
||||
if not (i.down or i.down_right or i.down_middle) {
|
||||
if c != null { pe_send(c, "pointerup", pe_btn) }
|
||||
pe_key = ""
|
||||
}
|
||||
} else if over != null {
|
||||
let b = pe_pressed(i, p)
|
||||
if b >= 0 { pe_down(over, b) } else if moved { pe_send(over, "pointermove", 0) }
|
||||
}
|
||||
if i.wheel != 0.0 and over != null { pe_send(over, "wheel", 0) }
|
||||
}
|
||||
function pe_pressed(i: UiInput, p: UiInput) -> int {
|
||||
if i.down and not p.down { return 0 }
|
||||
if i.down_right and not p.down_right { return 1 }
|
||||
if i.down_middle and not p.down_middle { return 2 }
|
||||
return -1
|
||||
}
|
||||
function pe_down(n: UiNode, b: int) -> void {
|
||||
pe_key = n.key
|
||||
pe_btn = b
|
||||
if not pe_send(n, "pointerdown", b) { pe_key = "" }
|
||||
}
|
||||
# one event: a native's input answers at once (false: not its to take); an element's on-<kind> runs
|
||||
# once the frame is drawn, with the pointer's fields on `event`
|
||||
function pe_send(n: UiNode, kind: string, b: int) -> bool {
|
||||
let e = new UiPointer
|
||||
e.kind = kind
|
||||
e.x = in_now.x - n.x
|
||||
e.y = in_now.y - n.y
|
||||
if in_last.x >= 0.0 {
|
||||
e.dx = in_now.x - in_last.x
|
||||
e.dy = in_now.y - in_last.y
|
||||
}
|
||||
e.button = b
|
||||
e.wheel = in_now.wheel
|
||||
if n.kind == UI_NATIVE and n.native != null and n.native.input != null { return n.native.input(n, e) }
|
||||
let v = Value.object()
|
||||
Value.put(v, "x", Value.float(e.x))
|
||||
Value.put(v, "y", Value.float(e.y))
|
||||
Value.put(v, "dx", Value.float(e.dx))
|
||||
Value.put(v, "dy", Value.float(e.dy))
|
||||
Value.put(v, "button", Value.int(b))
|
||||
Value.put(v, "wheel", Value.float(e.wheel))
|
||||
ui_fire(n, kind, v)
|
||||
return true
|
||||
}
|
||||
# a scroll box leaves the wheel to what is under the pointer inside it and takes it itself
|
||||
function pe_takes_wheel(box: UiNode, px: float, py: float) -> bool {
|
||||
let t = fr_pick(box, px, py, PK_WHEEL)
|
||||
return t != null and t != box
|
||||
}
|
||||
59
packages/ludic.ui/pointer_pick.ludic
Normal file
59
packages/ludic.ui/pointer_pick.ludic
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
# pointer_pick.ludic - what the pointer is on: the topmost element under it in painting order (a
|
||||
# positioned one by its z-index over the flow), and from there out to the nearest element that wants
|
||||
# what is asked - a control to press, or pointer events. pointer-events: none lets it through.
|
||||
const PK_CONTROL: int = 0
|
||||
const PK_POINTER: int = 1
|
||||
const PK_WHEEL: int = 2
|
||||
function fr_in(n: UiNode, px: float, py: float) -> bool {
|
||||
if n.ex != null and n.ex.no_pointer { return false }
|
||||
return px >= n.x and py >= n.y and px < n.x + n.cw and py < n.y + n.ch
|
||||
}
|
||||
# the nearest element that wants `kind`, out from the topmost one under the point
|
||||
function fr_pick(n: UiNode, px: float, py: float, kind: int) -> UiNode {
|
||||
let kids = fr_paint_rev(n)
|
||||
for i in 0 .. len(kids) {
|
||||
if fr_in(kids[i], px, py) {
|
||||
let r = fr_pick(kids[i], px, py, kind)
|
||||
if r != null { return r }
|
||||
return fr_wants(n, kind)
|
||||
}
|
||||
}
|
||||
return fr_wants(n, kind)
|
||||
}
|
||||
function fr_wants(n: UiNode, kind: int) -> UiNode {
|
||||
if kind == PK_CONTROL and (n.focusable or n.act != null) { return n }
|
||||
if kind == PK_POINTER and pe_wants(n, false) { return n }
|
||||
if kind == PK_WHEEL and pe_wants(n, true) { return n }
|
||||
return null
|
||||
}
|
||||
# the children the other way from how they are painted: positioned ones by z-index, highest first,
|
||||
# then the flow, last first; a popover is on the top layer, not here
|
||||
function fr_paint_rev(n: UiNode) -> []UiNode {
|
||||
let out = new []UiNode
|
||||
if n.children == null { return out }
|
||||
var lo = 1000000
|
||||
var hi = -1000000
|
||||
for i in 0 .. len(n.children) {
|
||||
let c = n.children[i]
|
||||
if c.pos != 0 and not c.popover {
|
||||
lo = Math.min(lo, c.z)
|
||||
hi = Math.max(hi, c.z)
|
||||
}
|
||||
}
|
||||
var z = hi
|
||||
while z >= lo {
|
||||
var i = len(n.children) - 1
|
||||
while i >= 0 {
|
||||
let c = n.children[i]
|
||||
if c.pos != 0 and not c.popover and c.z == z { push(out, c) }
|
||||
i -= 1
|
||||
}
|
||||
z -= 1
|
||||
}
|
||||
var k = len(n.children) - 1
|
||||
while k >= 0 {
|
||||
if n.children[k].pos == 0 and not n.children[k].popover { push(out, n.children[k]) }
|
||||
k -= 1
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
|
@ -50,7 +50,7 @@ function sc_input(n: UiNode, off0: float, room: float, most: float) -> float {
|
|||
if sc_scope != null and not sc_within(n, sc_scope) { return sc_into_view(n, off, room) }
|
||||
let i = in_now
|
||||
let over = i.x >= n.x and i.y >= n.y and i.x < n.x + n.cw and i.y < n.y + n.ch
|
||||
if over and i.wheel != 0.0 { off = off - i.wheel * ui_px(40.0) }
|
||||
if over and i.wheel != 0.0 and not pe_takes_wheel(n, i.x, i.y + off0) { off = off - i.wheel * ui_px(40.0) }
|
||||
off = sb_input(n, off, room, most)
|
||||
return sc_into_view(n, off, room)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -75,28 +75,8 @@ function sel_pseudo_ok(p: string, arg: string, n: UiNode) -> bool {
|
|||
if p == "empty" { return len(n.children) == 0 and n.text == "" }
|
||||
if p == "root" { return n.parent == null }
|
||||
if p == "checked" { return ui_attr_on(n, "checked") }
|
||||
if p == "active" { return n.hovered and fr_down_now }
|
||||
if p == "active" { return fr_active(n) }
|
||||
if p == "nth-child" { return sel_nth(arg, n.index + 1) }
|
||||
ui_err(`:{p}: there is no such pseudo-class here`)
|
||||
return false
|
||||
}
|
||||
# :nth-child(odd), (even), (3), (2n+1), (n+2), (-n+3): is the k-th child one of them
|
||||
function sel_nth(arg: string, k: int) -> bool {
|
||||
if arg == "odd" { return k % 2 == 1 }
|
||||
if arg == "even" { return k % 2 == 0 }
|
||||
var npos = -1
|
||||
for i in 0 .. len(arg) {
|
||||
if arg[i] == 110 { npos = i }
|
||||
}
|
||||
if npos < 0 { return string(k) == arg }
|
||||
var a = 1
|
||||
let head: string = arg[0..npos]
|
||||
if head == "-" { a = -1 } else if len(head) > 0 { a = int(el_num(head)) }
|
||||
var b = 0
|
||||
var tail = tpl_words(arg[npos + 1..len(arg)])
|
||||
if len(tail) > 0 and tail[0] == 43 { tail = tpl_words(tail[1..len(tail)]) }
|
||||
if len(tail) > 0 { b = int(el_num(tail)) }
|
||||
if a == 0 { return k == b }
|
||||
let d = k - b
|
||||
return d % a == 0 and d / a >= 0
|
||||
}
|
||||
|
|
|
|||
21
packages/ludic.ui/select_nth.ludic
Normal file
21
packages/ludic.ui/select_nth.ludic
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
# select_nth.ludic - :nth-child's formulas
|
||||
# :nth-child(odd), (even), (3), (2n+1), (n+2), (-n+3): is the k-th child one of them
|
||||
function sel_nth(arg: string, k: int) -> bool {
|
||||
if arg == "odd" { return k % 2 == 1 }
|
||||
if arg == "even" { return k % 2 == 0 }
|
||||
var npos = -1
|
||||
for i in 0 .. len(arg) {
|
||||
if arg[i] == 110 { npos = i }
|
||||
}
|
||||
if npos < 0 { return string(k) == arg }
|
||||
var a = 1
|
||||
let head: string = arg[0..npos]
|
||||
if head == "-" { a = -1 } else if len(head) > 0 { a = int(el_num(head)) }
|
||||
var b = 0
|
||||
var tail = tpl_words(arg[npos + 1..len(arg)])
|
||||
if len(tail) > 0 and tail[0] == 43 { tail = tpl_words(tail[1..len(tail)]) }
|
||||
if len(tail) > 0 { b = int(el_num(tail)) }
|
||||
if a == 0 { return k == b }
|
||||
let d = k - b
|
||||
return d % a == 0 and d / a >= 0
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue