Engine (examples/gamemaker): chase camera rig (camera({chase}) — ease-behind
with mouse-wins/recenter authority), writable camera + look deltas, spatial
queries (raycast/overlap_sphere/ground_normal), save/load, sustained tones,
rot_y + collide:false spawnables, HUD slots/bars, terrain noise shaping +
height bands, per-shape instanced render batching with static slabs (3.2x),
error push-loop into the agent chat, unknown-verb/option diagnostics with
game.splash:line:col positions, streaming tail-statement finalization, quiet
toolbar UI, Shh voice hush, fable voice.
Platform: runtime vector methods (.length/.normalized/.dot/.cross), scalar+
vector lerp, TAU; ScriptVm error capture sink; window frame capture API; four
headless-JIT fixes (scalar casts, mat4 mul, Id-arg expansion, commuted
scalar-vec ops) with regression test stages. Widgets: single-line TextInput
baseline centering, TextFlow inline-code baseline alignment, glass button
corner_radius uniform. Voice/ggml Metal backends: debug logs behind
GGML_METAL_TRACE. splashgame.md: the runtime-loaded game API contract.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
181 lines
4.3 KiB
Text
181 lines
4.3 KiB
Text
let image_results = []
|
|
|
|
let ResultCard = glass.Card{
|
|
width: Fill
|
|
height: Fit
|
|
flow: Right
|
|
spacing: 12
|
|
padding: 10
|
|
|
|
thumb := Image{
|
|
width: 128
|
|
height: 92
|
|
fit: ImageFit.CropToFill
|
|
}
|
|
|
|
// Named, so `info.title` / `info.source` are addressable from an instantiation.
|
|
// Named children are only fields of their *direct* parent, so an unnamed wrapper
|
|
// would make `title`/`source` unreachable.
|
|
info := View{
|
|
width: Fill
|
|
height: Fit
|
|
flow: Down
|
|
spacing: 5
|
|
|
|
title := glass.Body{
|
|
text: ""
|
|
width: Fill
|
|
}
|
|
|
|
source := glass.Caption{
|
|
text: ""
|
|
width: Fill
|
|
}
|
|
}
|
|
}
|
|
|
|
fn fetch(url, extra_headers){
|
|
let p = promise()
|
|
let headers = {
|
|
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36"
|
|
}
|
|
if extra_headers != nil {
|
|
for key, value in extra_headers {
|
|
headers[key] = value
|
|
}
|
|
}
|
|
net.http_request(net.HttpRequest{url: url method: net.HttpMethod.GET headers: headers}) do net.HttpEvents{
|
|
on_response: |res| p.resolve(res)
|
|
on_error: |_err| p.resolve(nil)
|
|
}
|
|
p
|
|
}
|
|
|
|
fn set_status(text){
|
|
ui.status.set_text(text)
|
|
}
|
|
|
|
fn search_images(query){
|
|
let clean = ("" + query).trim()
|
|
if clean == "" {
|
|
set_status("Enter a search query")
|
|
return
|
|
}
|
|
|
|
image_results.clear()
|
|
ui.results.render()
|
|
set_status("Searching DuckDuckGo images...")
|
|
|
|
let encoded = clean.url_encode()
|
|
let page = fetch("https://duckduckgo.com/?q=" + encoded + "&iax=images&ia=images", nil).await()
|
|
if page == nil {
|
|
set_status("Search failed")
|
|
return
|
|
}
|
|
|
|
let vqd = ""
|
|
let token_parts = page.body.to_string().split("vqd=\"")
|
|
if token_parts.len() > 1 {
|
|
vqd = token_parts[1].split("\"")[0]
|
|
}
|
|
if vqd == "" {
|
|
set_status("DuckDuckGo did not return an image token")
|
|
return
|
|
}
|
|
|
|
let response = fetch(
|
|
"https://duckduckgo.com/i.js?l=us-en&o=json&q=" + encoded + "&vqd=" + vqd + "&f=,,,,,&p=1",
|
|
{"Referer": "https://duckduckgo.com/"}
|
|
).await()
|
|
if response == nil {
|
|
set_status("Image request failed")
|
|
return
|
|
}
|
|
|
|
let data = response.body.to_string().parse_json()
|
|
if data != nil && data.results != nil {
|
|
for img in data.results {
|
|
if image_results.len() < 48 {
|
|
image_results.push({
|
|
title: img.title
|
|
source: img.source
|
|
thumbnail: img.thumbnail
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
if image_results.len() == 0 {
|
|
set_status("No images found")
|
|
} else {
|
|
set_status("" + image_results.len() + " images for " + clean)
|
|
}
|
|
ui.results.render()
|
|
}
|
|
|
|
View{
|
|
width: Fill
|
|
height: Fit
|
|
flow: Down
|
|
spacing: 14
|
|
padding: 4
|
|
|
|
View{
|
|
width: Fill
|
|
height: Fit
|
|
flow: Down
|
|
spacing: 4
|
|
glass.H1{text: "Picture Search"}
|
|
glass.Caption{text: "DUCKDUCKGO IMAGE RESULTS"}
|
|
}
|
|
|
|
glass.Card{
|
|
width: Fill
|
|
height: Fit
|
|
flow: Right
|
|
spacing: 10
|
|
padding: 12
|
|
|
|
query := glass.TextInput{
|
|
width: Fill
|
|
height: 38
|
|
empty_text: "Search for images..."
|
|
on_return: |text| search_images(text)
|
|
}
|
|
|
|
glass.GlassButtonProminent{
|
|
text: "Search"
|
|
height: 38
|
|
on_click: || search_images(ui.query.text())
|
|
}
|
|
}
|
|
|
|
status := glass.Caption{
|
|
text: "Type a query and press Search"
|
|
width: Fill
|
|
}
|
|
|
|
results := ScrollYView{
|
|
width: Fill
|
|
height: 610
|
|
flow: Down
|
|
spacing: 10
|
|
on_render: || {
|
|
if image_results.len() == 0 {
|
|
glass.Card{
|
|
width: Fill
|
|
height: 140
|
|
align: Align{x: 0.5 y: 0.5}
|
|
glass.Body{text: "Results will appear here."}
|
|
}
|
|
}
|
|
else for item in image_results {
|
|
ResultCard{
|
|
thumb.src: http_resource(item.thumbnail)
|
|
info.title.text: item.title
|
|
info.source.text: item.source
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|