Merge commit 'a299117' into lang/foundations
This commit is contained in:
commit
15f1020fb6
24 changed files with 71233 additions and 69256 deletions
|
|
@ -43,7 +43,7 @@ export function hints_mutes_load(hints_st: mut HintsState, l: Val) -> void {
|
|||
if Value.kind(e) != 4 { continue }
|
||||
let k = Value.as_str(e)
|
||||
let id = hints_find(k)
|
||||
if id >= 0 { hints_st.hn_mute[id] = true } else { push(hints_st.hn_foreign, k) }
|
||||
if id >= 0 { hints_st.hn_mute[id] = true } else { push(hints_st.hn_foreign, intern(k)) } # the tree is let go
|
||||
}
|
||||
}
|
||||
function hn_mutes() -> []bool {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ module ludic_i18n uses
|
|||
numbers float
|
||||
import "ports.ludic"
|
||||
import "state.ludic"
|
||||
import "tables.ludic"
|
||||
import "bytes.ludic"
|
||||
import "po.ludic"
|
||||
import "langs.ludic"
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ export function i18n_init(i18n_st: mut I18nState) -> void {
|
|||
i18n_st.names = new []string
|
||||
i18n_st.paths = new []string
|
||||
i18n_st.fonts = new []string
|
||||
i18n_st.tabs = new []I18nTables
|
||||
lang_add(i18n_st, "en", "English", "", "")
|
||||
let idx = Fs.read_text(i18n_st.lang_dir + "/languages.txt")
|
||||
if idx != null {
|
||||
|
|
@ -37,6 +38,7 @@ function lang_add(i18n_st: mut I18nState, code: string, name: string, path: stri
|
|||
i18n_st.names[i] = name
|
||||
i18n_st.paths[i] = path
|
||||
i18n_st.fonts[i] = font
|
||||
if tables_have(i18n_st, i) { i18n_st.tabs[i] = null } # another file for it: read that one next time
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ export state I18nState {
|
|||
plural: Dict = null # a plural msgid (the singular) -> index into forms / 3
|
||||
forms: []string = null # three forms per entry, "" where the file has fewer
|
||||
plural_rule: int = 0 # PL_*
|
||||
tabs: []I18nTables = null # by language: its lookups once read, kept for the next switch
|
||||
}
|
||||
|
||||
# where the shipped languages are (a directory with languages.txt and <code>.po), and the atlas a
|
||||
|
|
|
|||
88
packages/ludic.i18n/tables.ludic
Normal file
88
packages/ludic.i18n/tables.ludic
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
# tables.ludic - a language's lookups, kept once read: switching back to it is a copy of references,
|
||||
# not the .po parsed again (every change of language once dropped the old tables for good)
|
||||
property I18nTables {
|
||||
exact: Dict = null
|
||||
out: []string = null
|
||||
lits: []string = null
|
||||
pat_out: []string = null
|
||||
pat_l0: words = null
|
||||
pat_nl: words = null
|
||||
pat_sc: words = null
|
||||
pat_ph: words = null
|
||||
pat_n: int = 0
|
||||
pfx: Dict = null
|
||||
sfx: Dict = null
|
||||
gen_head: int = 0
|
||||
nx_p: words = null
|
||||
nx_s: words = null
|
||||
nx_g: words = null
|
||||
cache: Dict = null
|
||||
cache_out: []string = null
|
||||
caps: []string = null
|
||||
plural: Dict = null
|
||||
forms: []string = null
|
||||
plural_rule: int = 0
|
||||
}
|
||||
|
||||
@alloc_ok("once per language: its lookups, kept")
|
||||
function tables_keep(i18n_st: I18nState) -> I18nTables {
|
||||
let t = new I18nTables
|
||||
t.exact = i18n_st.exact
|
||||
t.out = i18n_st.out
|
||||
t.lits = i18n_st.lits
|
||||
t.pat_out = i18n_st.pat_out
|
||||
t.pat_l0 = i18n_st.pat_l0
|
||||
t.pat_nl = i18n_st.pat_nl
|
||||
t.pat_sc = i18n_st.pat_sc
|
||||
t.pat_ph = i18n_st.pat_ph
|
||||
t.pat_n = i18n_st.pat_n
|
||||
t.pfx = i18n_st.pfx
|
||||
t.sfx = i18n_st.sfx
|
||||
t.gen_head = i18n_st.gen_head
|
||||
t.nx_p = i18n_st.nx_p
|
||||
t.nx_s = i18n_st.nx_s
|
||||
t.nx_g = i18n_st.nx_g
|
||||
t.cache = i18n_st.cache
|
||||
t.cache_out = i18n_st.cache_out
|
||||
t.caps = i18n_st.caps
|
||||
t.plural = i18n_st.plural
|
||||
t.forms = i18n_st.forms
|
||||
t.plural_rule = i18n_st.plural_rule
|
||||
return t
|
||||
}
|
||||
|
||||
function tables_put(i18n_st: mut I18nState, t: I18nTables) -> void {
|
||||
i18n_st.exact = t.exact
|
||||
i18n_st.out = t.out
|
||||
i18n_st.lits = t.lits
|
||||
i18n_st.pat_out = t.pat_out
|
||||
i18n_st.pat_l0 = t.pat_l0
|
||||
i18n_st.pat_nl = t.pat_nl
|
||||
i18n_st.pat_sc = t.pat_sc
|
||||
i18n_st.pat_ph = t.pat_ph
|
||||
i18n_st.pat_n = t.pat_n
|
||||
i18n_st.pfx = t.pfx
|
||||
i18n_st.sfx = t.sfx
|
||||
i18n_st.gen_head = t.gen_head
|
||||
i18n_st.nx_p = t.nx_p
|
||||
i18n_st.nx_s = t.nx_s
|
||||
i18n_st.nx_g = t.nx_g
|
||||
i18n_st.cache = t.cache
|
||||
i18n_st.cache_out = t.cache_out
|
||||
i18n_st.caps = t.caps
|
||||
i18n_st.plural = t.plural
|
||||
i18n_st.forms = t.forms
|
||||
i18n_st.plural_rule = t.plural_rule
|
||||
}
|
||||
|
||||
function tables_have(i18n_st: I18nState, k: int) -> bool {
|
||||
if i18n_st.tabs == null or k >= len(i18n_st.tabs) { return false }
|
||||
return i18n_st.tabs[k] != null
|
||||
}
|
||||
|
||||
@alloc_ok("once per language: the slot its lookups are kept in")
|
||||
function tables_file(i18n_st: mut I18nState, k: int) -> void {
|
||||
if i18n_st.tabs == null { i18n_st.tabs = new []I18nTables }
|
||||
while len(i18n_st.tabs) <= k { push(i18n_st.tabs, null) }
|
||||
i18n_st.tabs[k] = tables_keep(i18n_st)
|
||||
}
|
||||
|
|
@ -114,6 +114,19 @@ program I18nTest {
|
|||
expect(i18n_font_now(i18n_st) == "font_default")
|
||||
}
|
||||
|
||||
test "a language switched back to is its kept tables, not its file read again; a new probe reads it" (i18n_st: mut I18nState, i18n_test_st: mut I18nTestState) {
|
||||
fresh(i18n_st, i18n_test_st)
|
||||
i18n_use(i18n_st, "tt")
|
||||
let first = L(i18n_st, "Settings")
|
||||
Fs.write_text(dir() + "/tt.po", header("Tongue", "") + po_line("Settings", "Changed"))
|
||||
i18n_use(i18n_st, "en")
|
||||
i18n_use(i18n_st, "tt")
|
||||
expect(L(i18n_st, "Settings") == first)
|
||||
i18n_init(i18n_st)
|
||||
i18n_use(i18n_st, "tt")
|
||||
expect(L(i18n_st, "Settings") == "Changed")
|
||||
}
|
||||
|
||||
test "a mod in the player's folder is found, replaces a shipped code, and goes when removed" (i18n_st: mut I18nState, i18n_test_st: mut I18nTestState) {
|
||||
fresh(i18n_st, i18n_test_st)
|
||||
Fs.write_text(mods() + "/zz.po", header("Mod Tongue", "") + po_line("Settings", "Mettings"))
|
||||
|
|
|
|||
|
|
@ -7,10 +7,16 @@ export function i18n_use(i18n_st: mut I18nState, code: string) -> void {
|
|||
let forced = I18nWorld.forced()
|
||||
if len(forced) > 0 { k = i18n_index(i18n_st, forced) }
|
||||
var font = ""
|
||||
if k > 0 {
|
||||
if k > 0 and tables_have(i18n_st, k) {
|
||||
tables_put(i18n_st, i18n_st.tabs[k])
|
||||
i18n_st.on = true
|
||||
i18n_st.cur = i18n_st.codes[k]
|
||||
font = i18n_st.fonts[k]
|
||||
} else if k > 0 {
|
||||
let text = Fs.read_text(i18n_st.paths[k])
|
||||
if text != null {
|
||||
i18n_load_text(i18n_st, text)
|
||||
tables_file(i18n_st, k)
|
||||
i18n_st.on = true
|
||||
i18n_st.cur = i18n_st.codes[k]
|
||||
font = i18n_st.fonts[k]
|
||||
|
|
|
|||
|
|
@ -20,6 +20,22 @@ export function settings_put(settings_st: SettingsState, v: Val, id: int) -> voi
|
|||
}
|
||||
}
|
||||
|
||||
# the store written into a kept object in place, in the file's own form: a Value is made only the
|
||||
# first time its key is seen (a screen reading the settings made a new tree on every change)
|
||||
export function settings_fill_json(settings_st: SettingsState, v: Val) -> void {
|
||||
for i in 0 .. len(settings_st.sg_defs) {
|
||||
let d = settings_st.sg_defs[i]
|
||||
if not d.saved { continue }
|
||||
if d.kind == SETTING_TEXT {
|
||||
value_set_str(v, d.key, settings_st.sg_text[i])
|
||||
} else if d.kind == SETTING_FLOAT {
|
||||
value_set_int(v, d.key, int(Math.floor(settings_st.sg_num[i] * 1000.0 + 0.5)))
|
||||
} else {
|
||||
value_set_int(v, d.key, int(settings_st.sg_num[i]))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# every key `v` names, held to its range; quiet, since a load is applied whole by its caller
|
||||
export function settings_from_json(settings_st: mut SettingsState, v: Val) -> void {
|
||||
if v == null { return }
|
||||
|
|
@ -30,7 +46,7 @@ function sg_read(settings_st: mut SettingsState, v: Val, id: int) -> void {
|
|||
let d = settings_st.sg_defs[id]
|
||||
if not d.saved or Value.kind(v) != 6 or Value.has(v, d.key) == 0 { return }
|
||||
if d.kind == SETTING_TEXT {
|
||||
settings_st.sg_text[id] = sv_str(v, d.key, settings_st.sg_text[id])
|
||||
settings_st.sg_text[id] = intern(sv_str(v, d.key, settings_st.sg_text[id])) # the file's tree is let go
|
||||
} else if d.kind == SETTING_FLOAT {
|
||||
settings_st.sg_num[id] = settings_held(settings_st, id, sv_float(v, d.key, settings_st.sg_num[id]))
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -35,6 +35,21 @@ program SettingsTest {
|
|||
expect_eq(fs[1].id, TOY_LANG)
|
||||
}
|
||||
|
||||
test "the store filled into a kept object changes its Values in place" (settings_st: mut SettingsState) {
|
||||
declare(settings_st)
|
||||
let o = Value.object()
|
||||
settings_fill_json(settings_st, o)
|
||||
let q = Value.get(o, "shadow_q")
|
||||
let l = Value.get(o, "lang")
|
||||
settings_set_int(settings_st, TOY_SHADOW_Q, 3)
|
||||
settings_set_text(settings_st, TOY_LANG, "tr")
|
||||
settings_fill_json(settings_st, o)
|
||||
expect(Value.get(o, "shadow_q") == q)
|
||||
expect(Value.get(o, "lang") == l)
|
||||
expect_eq(Value.as_int(q), 3)
|
||||
expect(Value.as_str(l) == "tr")
|
||||
}
|
||||
|
||||
test "a value out of range is the default" (settings_st: mut SettingsState) {
|
||||
declare(settings_st)
|
||||
settings_set_int(settings_st, TOY_SHADOW_Q, 9)
|
||||
|
|
|
|||
|
|
@ -51,6 +51,8 @@ export state UiState {
|
|||
ev_ring_at: int = 0
|
||||
nt_evs: []Val = nt_evs_new() # native.ludic: fired events' objects, NT_EVS of them, made with the state
|
||||
nt_ev_at: int = 0
|
||||
nt_nums: []Val = nt_nums_new() # fired.ludic: the numbers a scroll or a slider reports, a ring
|
||||
nt_num_at: int = 0
|
||||
hd_ev: Val = hd_ev_new() # hold.ludic: the held control's event, filled in place
|
||||
cv_texts: []string = new []string # units_pieces.ludic: a value's var() pieces, kept
|
||||
tx_pieces: [][]string = new [][]string # eval_text.ludic: a text's pieces, a list a depth
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ function ct_range_set(ui_st: mut UiState, n: UiNode, v0: float) -> void {
|
|||
let lo = Value.as_float(ui_attr(n, "min"))
|
||||
let hi = Value.as_float(ui_attr(n, "max"))
|
||||
let v = Math.clamp(v0, lo, Math.max(hi, lo))
|
||||
if v != Value.as_float(ui_attr(n, "value")) { ui_fire(ui_st, n, "change", Value.float(v)) }
|
||||
if v != Value.as_float(ui_attr(n, "value")) { ui_fire_float(ui_st, n, "change", v) }
|
||||
}
|
||||
# a range's track pressed or dragged: the value where the pointer is
|
||||
function ct_press(ui_st: mut UiState, n: UiNode, x: float, y: float) -> void {
|
||||
|
|
|
|||
16
packages/ludic.ui/fired.ludic
Normal file
16
packages/ludic.ui/fired.ludic
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
# fired.ludic - the number a control reports on each frame it moves (a box scrolled, a slider dragged),
|
||||
# from a ring of Values kept with the state: a fresh Value.float a frame was kept by nothing. The
|
||||
# queue runs within the frame, long before NT_NUMS more come round to one
|
||||
const NT_NUMS: int = 64
|
||||
function nt_nums_new() -> []Val {
|
||||
let l = new []Val
|
||||
for i in 0 .. NT_NUMS { push(l, Value.float(0.0)) }
|
||||
return l
|
||||
}
|
||||
export function ui_fire_float(ui_st: mut UiState, n: UiNode, name: string, x: float) -> void {
|
||||
let at = ui_st.nt_num_at % NT_NUMS
|
||||
ui_st.nt_num_at = at + 1
|
||||
let v = ui_st.nt_nums[at]
|
||||
Value.be_float(v, x)
|
||||
ui_fire(ui_st, n, name, v)
|
||||
}
|
||||
|
|
@ -24,6 +24,7 @@ import "env.ludic"
|
|||
import "eval.ludic"
|
||||
import "act.ludic"
|
||||
import "native.ludic"
|
||||
import "fired.ludic"
|
||||
import "native_room.ludic"
|
||||
import "controls.ludic"
|
||||
import "controls_build.ludic"
|
||||
|
|
|
|||
|
|
@ -13,7 +13,8 @@ function ly_ratio(n: UiNode, fixed_w: float, avail: float) -> void {
|
|||
}
|
||||
# x, y, w, h: a `nat_w` x `nat_h` picture in the node's content box by its object-fit
|
||||
export function ui_object_fit(n: UiNode, nat_w: float, nat_h: float) -> []float { return ui_object_fit_into(new []float, n, nat_w, nat_h) }
|
||||
function ui_object_fit_into(out: []float, n: UiNode, nat_w: float, nat_h: float) -> []float {
|
||||
# ... into the caller's kept list (a draw every frame should: ui_object_fit makes one a call)
|
||||
export function ui_object_fit_into(out: []float, n: UiNode, nat_w: float, nat_h: float) -> []float {
|
||||
let bx = n.x + n.pl + n.bl
|
||||
let by = n.y + n.pt + n.bt
|
||||
let bw = n.cw - ly_hin(n)
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ function sc_walk(ui_st: mut UiState, n: UiNode, live: bool) -> void {
|
|||
var off = held
|
||||
if live { off = sc_input(ui_st, n, off, room, most) }
|
||||
off = Math.clamp(off, 0.0, most)
|
||||
if live and off != Math.clamp(held, 0.0, most) { ui_fire(ui_st, n, "scroll", Value.float(off / ui_px(ui_st, 1.0))) }
|
||||
if live and off != Math.clamp(held, 0.0, most) { ui_fire_float(ui_st, n, "scroll", off / ui_px(ui_st, 1.0)) }
|
||||
sc_put(ui_st, n.key, off)
|
||||
n.scroll_y = off
|
||||
if off != 0.0 {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue