Skip to main content

Indie game storeFree gamesFun gamesHorror games
Game developmentAssetsComics
SalesBundles
Jobs
TagsGame Engines

The Contraption Bazaar Sticky

A topic by Internet Janitor created Feb 28, 2023 Views: 7,116 Replies: 78
Viewing posts 1 to 30
Developer (1 edit) (+7)

Seeking fancy new contraptions for your decks? Seek no further, friend! Have a look at my wares, ready to copy and paste right into Decker:

The "marquee" contraption:

Configure your choice of text and size! Respects the "font" setting for contraption instances.

%%WGT0{"w":[{"name":"marquee1","type":"contraption","size":[100,100],"pos":[391,195],"def":"marquee","widgets":{"canvas1":{},"text":{}}}],"d":{"marquee":{"name":"marquee","size":[100,100],"resizable":1,"margin":[5,5,5,5],"description":"A horizontally-scrolling text marquee.","script":"on set_text x do text.value:x end\non get_text do text.value end","attributes":{"name":["text"],"label":["Text"],"type":["rich"]},"widgets":{"canvas1":{"type":"canvas","size":[100,100],"pos":[0,0],"locked":1,"animated":1,"script":"on view do\n me.font:card.font\n t:me.font.textsize[text.text][0]\n w:me.size[0]+t\n me.clear[]\n me.text[text.text (me.size/1,2)-(w%sys.ms/10),0 \"center_left\"]\nend","scale":1},"text":{"type":"field","size":[27,25],"pos":[1,-57],"locked":1,"show":"none","value":"Your Text Here!"}}}}}

The "flag" contraption:

Add your favorite horizontally-striped flags to your deck, big or small, square or rectangular. The color list may use any of the built-in named colors ( whiteblackyelloworangeredmagentapurplebluecyangreendarkgreenbrowntanlightgraymediumgraydarkgray ) or pattern indices (0-47).

%%WGT0{"w":[{"name":"flag1","type":"contraption","size":[100,100],"pos":[206,121],"def":"flag","widgets":{"canv":{},"val":{}}}],"d":{"flag":{"name":"flag","size":[100,100],"resizable":1,"margin":[9,9,9,9],"description":"a resizable flag made of multicolored horizontal strips.","script":"on get_value do val.text end\non set_value x do val.text:x view[] end\non view do\n c:each c in \",\" split val.text\n  (0+c) unless colors[c]\n end\n canv.clear[]\n s:canv.size[1]/count c\n each v i in c\n  canv.pattern:v\n  canv.rect[0,s*i 1+canv.size[0],s] \n end\nend","attributes":{"name":["value"],"label":["Colors\n(Comma\nSeparated)"],"type":["code"]},"widgets":{"canv":{"type":"canvas","size":[100,100],"pos":[0,0],"pattern":37,"scale":1},"val":{"type":"field","size":[90,20],"pos":[1,-31],"locked":1,"border":0,"style":"plain","align":"center","value":"red,orange,yellow,green,blue,purple"}}}}}

The "patternPicker" contraption:


A grid of swatches- click one to choose a pattern and fire a change[] event! Configurable to include or exclude colors. If you're looking for a little scripting exercise, try using a patternPicker to make a fancier editor for the flag contraption!

%%WGT0{"w":[{"name":"patternPicker1","type":"contraption","size":[100,100],"pos":[48,107],"def":"patternPicker","widgets":{"c":{},"v":{},"cc":{}}}],"d":{"patternPicker":{"name":"patternPicker","size":[100,100],"resizable":1,"margin":[10,10,10,10],"description":"a palette of patterns and (optionally) colors.","script":"on get_value do v.value end\non set_value x do v.value:x view[] end\non get_color do cc.value end\non set_color x do cc.value:x view[] end\n\nb:5\nr:4+2*cc.value\ns:floor(c.size-2*b)/8,r\n\non boxes do\n flip b+s*flip 8 cross r\nend\n\non view do\n o:-50\n c.clear[]\n each p i in boxes[]\n  c.pattern:i\n  c.rect[p s]\n  c.pattern:1\n  c.box[p s+1]\n  if i=v.value o:p end\n end\n c.pattern:0\n c.box[o-1 s+3]\n c.pattern:1\n c.box[o-2 s+5]\nend\n\non inside p r do\n min(p>r)&(r+s>p)\nend\non click pos do\n each p i in boxes[]\n  if inside[pos p]\n   set_value[i]\n   card.event[\"change\" i]\n  end\n end\nend\n","template":"on change val do\n \nend","attributes":{"name":["value","color"],"label":["Value","Color"],"type":["number","bool"]},"widgets":{"c":{"type":"canvas","size":[100,100],"pos":[0,0],"locked":1,"border":0,"scale":1},"v":{"type":"slider","size":[60,25],"pos":[2,-41],"locked":1,"show":"none","interval":[0,47]},"cc":{"type":"button","size":[14,20],"pos":[73,-40],"locked":1,"show":"none","style":"check","value":1}}}}}

Have some contraptions of your own to share? There's plenty of room in the Bazaar!

Developer (1 edit) (+2)

How about,

The "DVD ScreenSaver" contraption:


Gracefully sliding around the card of your choice forever! Includes a "bump" event fired every time it bounces off an edge of the screen. If you edit the prototype, you can make this use whatever image you like.

%%WGT0{"w":[{"name":"dvdScreenSaver1","type":"contraption","size":[101,58],"pos":[192,134],"show":"transparent","def":"dvdScreenSaver","widgets":{"vel":{"value":"[-3,-2]"}}}],"d":{"dvdScreenSaver":{"name":"dvdScreenSaver","size":[101,58],"margin":[0,0,0,0],"description":"just the dvd screen saver.","script":"on set_vel x do vel.text:\"%j\" format list x end\non get_vel do \"%j\" parse vel.text end\n\non view do\n v:get_vel[]\n p:v+card.pos\n s:(512,342)-card.size\n if !p[0]>0    v[0]:-v[0] b:1 end\n if !p[1]>0    v[1]:-v[1] b:1 end\n if  p[0]>s[0] v[0]:-v[0] b:1 end\n if  p[1]>s[1] v[1]:-v[1] b:1 end\n card.pos:(s-1)&0|p\n set_vel[v]\n if b card.event[\"bump\"] end\nend\n","template":"on bump do\n \nend","image":"%%IMG0AGUAOgB//////AAA////gAAAf/////wAAf////gAAP/////+AAP////+AAD//////gAH/////4AA//////4AB//////AAP//////AA//////4AAAAH///wAf+AAB/+AAAAAf//8AP/AAAH/wAf+AD///gH/n/gA/8AH/gA/+/4D/z/wAP/gB/wAH/v+A/8/8AB/4A/8AB/7/gf+P/AAf+AP/AAf+f8P/D/wAH/gD/wAP/n/H/g/4AD/wB/4AD/5/z/wf+AA/8Af+AB/8P+/4H/gAf/AH/gA//D//8B/4AP/gB/wA//gf/+Af8AP/wA/8A//wH//AP/AP/8AP/B//4B//wD/x//+AD////4AP/4A////+AA////8AD/8AP////AAP///4AA/+AH////AAH///gAAH/AB///+AAB//8AAAB/gAf//8AAAAAAAAAAfwAAAAAAAAAAAAAAAD4AAAAAAAAAAAAAAAA8AAAAAAAAAAAAAAAAHAAAAAAAAAAAAAAAABgAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAP/////+AAAAAAAAA//////////AAAAAA////////////8AAAD/////////////8AAH/////+AAf/////4AH/////AAAAP/////gD/////gAAAB/////4Af////+AAAA/////+AD/////4AAB//////AAP//////B///////AAAH////////////4AAAAD///////////AAAAAAAP////////AAdsAAAAAAf////AAAACVAAAAAAAAAAAAAAAAkQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwGBwP4Af+AHwAAAAAODgcD/gH/gH/AAAAADhwHAw8BwADg4AAAAAccBwMHgcABwHAAAAADOAcDA4H/AYAwAAAAA7AHAwOB/wGAMAAAAAHwBwMHgcABwHAAAAAB4AcDDwHAAODgAAAAAOAHA/4B/4B/wAAAAADABwP4Af+AHwAAAA=","attributes":{"name":[],"label":[],"type":[]},"widgets":{"vel":{"type":"field","size":[37,13],"pos":[2,-19],"locked":1,"animated":1,"show":"none","style":"plain","align":"center","value":"[3,2]"}}}}}
(+3)

ur a legend

Developer

Thank you. :)

(+2)

sorry, i think I must have missed something. how should I paste these into a deck?

Developer (1 edit) (+3)

Copy the code starting at the "%%WGT" part and ending with the last "}" to your clipboard, open Decker, and then choose "Edit -> Paste Widgets" (or just "Edit -> Paste" in web-decker) from the main menu.

The code blocks on this page are what it looks like when you copy and paste one or more widgets in Decker. When you copy a Contraption to the clipboard, it carries along the associated Prototype (contraption definition), which makes it possible to share them like this or easily copy and paste them between decks. The same idea applies to copying and pasting cards.

Contraptions were added in v1.12, so if you downloaded an older version of Decker, please make sure you upgrade!

Does that clear things up?

(+1)

yes that absolutely does! thank you for being so thorough in your reply! i was also tripped up by needing to leave the widget tool for them to start doing their thing, but that one's on me

Developer (1 edit) (+1)

A potentially more generally useful variant of the DVD ScreenSaver Contraption,

The Bouncer Contraption:



Same basic idea as its predecessor, but now you can paste a custom image into each instance, and that image will be bounced within the boundary of the contraption's (resizable) bounding box.

%%WGT0{"w":[{"name":"bouncer1","type":"contraption","size":[260,188],"pos":[126,68],"def":"bouncer","widgets":{"c":{"size":[101,58],"pos":[42,53]},"i":{"size":[100,32],"pos":[280,-6]},"p":{"size":[100,38],"pos":[280,45],"value":"{\"v\":[3,-2],\"p\":[42,53]}"}}}],"d":{"bouncer":{"name":"bouncer","size":[100,100],"resizable":1,"margin":[0,0,0,0],"description":"bounce an image around within a bounding rectangle","script":"on get_img     do i.value   end\non set_img x   do i.value:x end\non get_state   do p.text    end\non set_state x do p.text:x  end\n\non view do\n img:first extract arg where arg..type=\"image\" from i.value\n s:\"%j\" parse p.text\n b:card.size-img.size\n if !\"v\" in s  s.v:3,2 end\n if !\"p\" in s  s.p:b/2 end\n \n s.p:s.p+s.v\n if !s.p[0]>0    s.v[0]:-s.v[0] end\n if !s.p[1]>0    s.v[1]:-s.v[1] end\n if  s.p[0]>b[0] s.v[0]:-s.v[0] end\n if  s.p[1]>b[1] s.v[1]:-s.v[1] end\n s.p:(b-1)&0|s.p\n p.text:\"%j\" format s\n \n c.size:img.size\n c.pos:s.p\n c.clear[]\n c.paste[img 0,0 1]\nend\n","attributes":{"name":["img","state"],"label":["Image","State"],"type":["rich","string"]},"widgets":{"c":{"type":"canvas","size":[36,35],"pos":[120,53],"locked":1,"animated":1,"show":"transparent","border":0,"scale":1},"i":{"type":"field","size":[100,20],"pos":[120,-6],"show":"none","value":{"text":["","i"],"font":["",""],"arg":["","%%IMG0AGUAOgB//////AAA////gAAAf/////wAAf////gAAP/////+AAP////+AAD//////gAH/////4AA//////4AB//////AAP//////AA//////4AAAAH///wAf+AAB/+AAAAAf//8AP/AAAH/wAf+AD///gH/n/gA/8AH/gA/+/4D/z/wAP/gB/wAH/v+A/8/8AB/4A/8AB/7/gf+P/AAf+AP/AAf+f8P/D/wAH/gD/wAP/n/H/g/4AD/wB/4AD/5/z/wf+AA/8Af+AB/8P+/4H/gAf/AH/gA//D//8B/4AP/gB/wA//gf/+Af8AP/wA/8A//wH//AP/AP/8AP/B//4B//wD/x//+AD////4AP/4A////+AA////8AD/8AP////AAP///4AA/+AH////AAH///gAAH/AB///+AAB//8AAAB/gAf//8AAAAAAAAAAfwAAAAAAAAAAAAAAAD4AAAAAAAAAAAAAAAA8AAAAAAAAAAAAAAAAHAAAAAAAAAAAAAAAABgAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAP/////+AAAAAAAAA//////////AAAAAA////////////8AAAD/////////////8AAH/////+AAf/////4AH/////AAAAP/////gD/////gAAAB/////4Af////+AAAA/////+AD/////4AAB//////AAP//////B///////AAAH////////////4AAAAD///////////AAAAAAAP////////AAdsAAAAAAf////AAAACVAAAAAAAAAAAAAAAAkQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwGBwP4Af+AHwAAAAAODgcD/gH/gH/AAAAADhwHAw8BwADg4AAAAAccBwMHgcABwHAAAAADOAcDA4H/AYAwAAAAA7AHAwOB/wGAMAAAAAHwBwMHgcABwHAAAAAB4AcDDwHAAODgAAAAAOAHA/4B/4B/wAAAAADABwP4Af+AHwAAAA="]}},"p":{"type":"field","size":[100,20],"pos":[120,24],"show":"none","style":"plain"}}}}}
Developer (1 edit) (+2)

Introducing

The "macWindow" contraption:

A resizable window frame that resembles System 6.

If you make the contraption transparent, you can draw underneath the frame. If you leave it "solid", you can overlap it on other windows. The title is configurable, and "close" and "resize" events are fired by clicking on the corner buttons.

While this contraption is not a "true" window, it can be a handy decoration or the beginnings of a more realistic simulacrum.

%%WGT0{"w":[{"name":"win","type":"contraption","size":[59,71],"pos":[211,143],"def":"macWindow","widgets":{"close":{},"resize":{},"title":{}}}],"d":{"macWindow":{"name":"macWindow","size":[59,71],"resizable":1,"margin":[22,34,33,33],"description":"a window frame in the style of MacOS 6.","script":"on get_title do title.text end\non set_title x do title.text:x end\n\non view do\n t:get_title[]\n if count t\n  title.font:\"menu\"\n  s:first title.font.textsize[t]\n  title.size:(s+10),title.size[1]\n  title.pos :(.5*card.size[0]-title.size[0]),title.pos[1]\n  title.show:\"solid\"\n else\n  title.show:\"none\"\n end\nend","template":"on close do\n \nend\n\non resize do\n \nend","image":"%%IMG2ADsARwE8IDgBAyA4AQMgOAEDIAEBBiABAQsgAQEQIAEBCyABAQYgAQEDIAgBASAJAQEgEgEBIAUBASADAQEgCAEDIAEBBiABAQEgCQEBIAEBECABAQEgBQEBIAMBASABAQYgAQEDIAgBASAJAQEgEgEBIAUBASADAQEgCAEDIAEBBiABAQEgCQEBIAEBECABAQEgBQEBIAMBASABAQYgAQEDIAgBASAJAQEgEgEBIAUBASADAQEgCAEDIAEBBiABAQEgCQEBIAEBECABAQcgAwEBIAEBBiABAQMgCAEBIAkBASASAQEgCQEBIAgBAyABAQYgAQEBIAkBASABARAgAQEBIAkBASABAQYgAQEDIAgBASAJAQEgEgEBIAkBASAIAQMgAQEGIAEBCyABARAgAQELIAEBBiABAQMgOAEDIDgBAyA4AT4AKQEBIA4BAwApAQEgBgEBIAcBAwApAQEgBQEBIAEBASAGAQMAKQEBIAQBASADAQEgBQEDACkBASADAQEgBQEBIAQBAwApAQEgAgEBIAcBASADAQMAKQEBIAEBASAJAQEgAgEDACkBBSAFAQQgAQEDACkBASADAQEgBQEBIAQBAwApAQEgAwEBIAUBASAEAQMAKQEBIAMBASAFAQEgBAEDACkBASADAQcgBAEDACkBASAOAQMAKQEBIA4BAwApARIAKQEBIA4BAwApAQEgDgEDACkBASAOAQMAKQEBIA4BAwApAQEgDgEDACkBEgApAQEgDgEDACkBASAOAQMAKQEBIAMBByAEAQMAKQEBIAMBASAFAQEgBAEDACkBASADAQEgBQEBIAQBAwApAQEgAwEBIAUBASAEAQMAKQEFIAUBBCABAQMAKQEBIAEBASAJAQEgAgEDACkBASACAQEgBwEBIAMBAwApAQEgAwEBIAUBASAEAQMAKQEBIAQBASADAQEgBQEDACkBASAFAQEgAQEBIAYBAwApAQEgBgEBIAcBAwApAQEgDgE+IAcBASAGAQEgCwEBIAYBASAHAQEgDgEDIAYBAiAGAQEgCwEBIAYBAiAGAQEgDgEDIAUBASABAQEgBgEBIAsBASAGAQEgAQEBIAUBASACAQcgBQEDIAQBASACAQUgAgEBIAsBASACAQUgAgEBIAQBASACAQEgBQEBIAUBAyADAQEgBwEBIAIBASALAQEgAgEBIAcBASADAQEgAgEBIAUBBSABAQMgAgEBIAgBASACAQEgCwEBIAIBASAIAQEgAgEBIAIBASAFAQEgAwEBIAEBAyABAQEgCQEBIAIBASALAQEgAgEBIAkBASABAQEgAgEBIAUBASADAQEgAQEDIAIBASAIAQEgAgEBIAsBASACAQEgCAEBIAIBASACAQEgBQEBIAMBASABAQMgAwEBIAcBASACAQEgCwEBIAIBASAHAQEgAwEBIAIBByADAQEgAQEDIAQBASACAQUgAgEBIAsBASACAQUgAgEBIAQBASAEAQEgBwEBIAEBAyAFAQEgAQEBIAYBASALAQEgBgEBIAEBASAFAQEgBAEBIAcBASABAQMgBgECIAYBASALAQEgBgECIAYBASAEAQEgBwEBIAEBAyAHAQEgBgEBIAsBASAGAQEgBwEBIAQBCSABAQMgDgEBIAsBASAOAQEgDgE9AAEBOg==","attributes":{"name":["title"],"label":["Title"],"type":["string"]},"widgets":{"close":{"type":"button","size":[11,11],"pos":[9,4],"script":"on click do\n card.event[\"close\"]\nend","style":"invisible"},"resize":{"type":"button","size":[11,11],"pos":[38,4],"script":"on click do\n card.event[\"resize\"]\nend","style":"invisible"},"title":{"type":"field","size":[44,17],"pos":[-50,1],"locked":1,"font":"menu","show":"none","border":0,"style":"plain","align":"center"}}}}}
(+6)


I introduced Interact dragging and resizing to macWindow, and included a rich text field to demonstrate how widgets can be embedded in such contraptions!

%%WGT0{"w":[{"name":"macFieldWindow1","type":"contraption","size":[99,92],"pos":[355,214],"def":"macFieldWindow","widgets":{"close":{},"resize":{"pos":[78,4]},"title":{"size":[37,17],"pos":[31,1],"value":"Hello"},"drag_resize":{"pos":[83,76]},"field":{"size":[98,58],"value":{"text":["I am a draggable and resizeable window sporting a rich text field!\n","Try me!"],"font":["","menu"],"arg":["",""]}},"bar":{"size":[99,19],"image":"%%IMG2AGMAEwD/AP8A/wD/AP8A/wD/AGA="}}}],"d":{"macFieldWindow":{"name":"macFieldWindow","size":[59,71],"resizable":1,"margin":[22,34,33,33],"description":"a window frame in the style of MacOS 6, sporting a field as well as supporting dragging and resizing! The internal field is available  at x.field","script":"on get_title do title.text end\non set_title x do title.text:x end\non get_field do field end\non get_value do field.value end\non set_value x do field.value: x end\n\non view do\n field.locked:card.locked\n t:get_title[]\n if count t\n  title.font:\"menu\"\n  s:first title.font.textsize[t]\n  title.size:(s+10),title.size[1]\n  title.pos :(.5*card.size[0]-title.size[0]),title.pos[1]\n  title.show:\"solid\"\n else\n  title.show:\"none\"\n end\nend","template":"on close do\n \nend\n\non resize do\n \nend","image":"%%IMG2ADsARwE8IDgBAyA4AQMgOAEDIAEBBiABAQsgAQEQIAEBCyABAQYgAQEDIAgBASAJAQEgEgEBIAUBASADAQEgCAEDIAEBBiABAQEgCQEBIAEBECABAQEgBQEBIAMBASABAQYgAQEDIAgBASAJAQEgEgEBIAUBASADAQEgCAEDIAEBBiABAQEgCQEBIAEBECABAQEgBQEBIAMBASABAQYgAQEDIAgBASAJAQEgEgEBIAUBASADAQEgCAEDIAEBBiABAQEgCQEBIAEBECABAQcgAwEBIAEBBiABAQMgCAEBIAkBASASAQEgCQEBIAgBAyABAQYgAQEBIAkBASABARAgAQEBIAkBASABAQYgAQEDIAgBASAJAQEgEgEBIAkBASAIAQMgAQEGIAEBCyABARAgAQELIAEBBiABAQMgOAEDIDgBAyA4AT4AKQEBIA4BAwApAQEgBgEBIAcBAwApAQEgBQEBIAEBASAGAQMAKQEBIAQBASADAQEgBQEDACkBASADAQEgBQEBIAQBAwApAQEgAgEBIAcBASADAQMAKQEBIAEBASAJAQEgAgEDACkBBSAFAQQgAQEDACkBASADAQEgBQEBIAQBAwApAQEgAwEBIAUBASAEAQMAKQEBIAMBASAFAQEgBAEDACkBASADAQcgBAEDACkBASAOAQMAKQEBIA4BAwApARIAKQEBIA4BAwApAQEgDgEDACkBASAOAQMAKQEBIA4BAwApAQEgDgEDACkBEgApAQEgDgEDACkBASAOAQMAKQEBIAMBByAEAQMAKQEBIAMBASAFAQEgBAEDACkBASADAQEgBQEBIAQBAwApAQEgAwEBIAUBASAEAQMAKQEFIAUBBCABAQMAKQEBIAEBASAJAQEgAgEDACkBASACAQEgBwEBIAMBAwApAQEgAwEBIAUBASAEAQMAKQEBIAQBASADAQEgBQEDACkBASAFAQEgAQEBIAYBAwApAQEgBgEBIAcBAwApAQEgDgE+IAcBASAGAQEgCwEBIAYBASAHAQEgDgEDIAYBAiAGAQEgCwEBIAYBAiAGAQEgDgEDIAUBASABAQEgBgEBIAsBASAGAQEgAQEBIAUBASACAQcgBQEDIAQBASACAQUgAgEBIAsBASACAQUgAgEBIAQBASACAQEgBQEBIAUBAyADAQEgBwEBIAIBASALAQEgAgEBIAcBASADAQEgAgEBIAUBBSABAQMgAgEBIAgBASACAQEgCwEBIAIBASAIAQEgAgEBIAIBASAFAQEgAwEBIAEBAyABAQEgCQEBIAIBASALAQEgAgEBIAkBASABAQEgAgEBIAUBASADAQEgAQEDIAIBASAIAQEgAgEBIAsBASACAQEgCAEBIAIBASACAQEgBQEBIAMBASABAQMgAwEBIAcBASACAQEgCwEBIAIBASAHAQEgAwEBIAIBByADAQEgAQEDIAQBASACAQUgAgEBIAsBASACAQUgAgEBIAQBASAEAQEgBwEBIAEBAyAFAQEgAQEBIAYBASALAQEgBgEBIAEBASAFAQEgBAEBIAcBASABAQMgBgECIAYBASALAQEgBgECIAYBASAEAQEgBwEBIAEBAyAHAQEgBgEBIAsBASAGAQEgBwEBIAQBCSABAQMgDgEBIAsBASAOAQEgDgE9AAEBOg==","attributes":{"name":["title","value"],"label":["Title","Text"],"type":["string","rich"]},"widgets":{"close":{"type":"button","size":[11,11],"pos":[9,4],"script":"on click do\n card.event[\"close\"]\nend","style":"invisible"},"resize":{"type":"button","size":[11,11],"pos":[38,4],"script":"on click do\n card.event[\"resize\"]\nend","style":"invisible"},"title":{"type":"field","size":[8,17],"pos":[-12,1],"locked":1,"font":"menu","show":"none","border":0,"style":"plain","align":"center"},"drag_resize":{"type":"canvas","size":[14,14],"pos":[43,55],"script":"on click pos do\n field.locked:1\nend\n\non drag pos do\n card.size:(me.offset-card.pos-16)|(title.size[0]+57),85\n view[]\n field.locked:1\nend\n\non release pos do\n field.locked:card.locked\nend","show":"transparent","border":0,"image":"%%IMG2AA4ADgDE","draggable":1,"scale":1},"field":{"type":"field","size":[58,37],"pos":[0,18],"border":1,"scrollbar":1},"bar":{"type":"canvas","size":[59,19],"pos":[0,0],"script":"on click pos do\n field.locked:1\nend\n\non drag pos do\n card.pos:me.offset\nend\n\non release pos do\n field.locked:card.locked\nend","show":"transparent","border":0,"image":"%%IMG2ADsAEwD/AP8A/wD/AGU=","draggable":1,"scale":1}}}}}
(1 edit) (+1)

Hello! I was wondering how exactly one might embed widgets in the rich text field of your component. I'm very new to all this, so learning new tricks is a consistent struggle haha. But thanks for all the work y'all do making such neat stuff! And also thanks in advance for your help ;-;

(+2)

I think the behaviour you're after can be achieved by making sure the extra widget's edges are aligned properly with the Prototype's margins so that the auto-resizing behaviour still works. If you need more complicated relative positioning than that, you'll probably need to change the script of the resize widget ("drag_resize") so that it applies the transformations the same way that it currently locks and unlocks the Field

Thanks!

Hello! I know it seems a bit too late to ask? :')) But, for some reason I can't seem to get the contraption to close whenever I click the (left) corner button and im not sure how to fix it. (Im still kinda new to Decker.)

Developer(+2)

The "close" and "resize" buttons of this contraption don't do anything by default, but you can add your own behaviors with scripting.

If you open the properties panel for the MacWindow (double click on it while in Widgets mode) and click "Script..." you'll see the template script of the contraption:

on close do
end
on resize do
end

Fill in "on close ..." to make it hide the contraption:

on close do
 me.show:"none"
end

It's easy to un-hide the window again later in another script:

myMacWindow.show:"solid"

If you really want to destroy the contraption instance when you click the "close" button you can remove it from the deck:

on close do
 deck.remove[me]
end

Does that make sense?

(+2)

Ohh okay! Got it. Thank you for the quick feedback!

Developer(+2)

Now Serving,

The EggTimer Contraption

A countdown timer for a configurable number of seconds, firing a "finish[]" event and inverting in color when it completes. Clicking a second time resets the countdown.

%%WGT0{"w":[{"name":"eggtimer1","type":"contraption","size":[60,20],"pos":[226,161],"def":"eggtimer","widgets":{"b":{},"s":{},"a":{}}}],"d":{"eggtimer":{"name":"eggtimer","size":[60,20],"resizable":1,"margin":[5,5,5,5],"description":"a configurable countdown timer.","script":"on get_seconds do 0+s.text end\non set_seconds x do s.text:x end\n\non view do\n b.font:card.font\n if b.animated\n  e:(sys.ms-a.text)/1000\n  b.text:r:floor 0|get_seconds[]-e\n  if r=0\n   if b.show=\"solid\" card.event[\"finish\"] end\n   b.show:\"invert\"\n  end\n else\n  b.show:\"solid\"\n  b.text:s.text\n end\nend\n\non click do\n a.text:sys.ms\n b.animated:!b.animated\n view[]\nend","template":"on finish do\n \nend","attributes":{"name":["seconds"],"label":["Seconds"],"type":["number"]},"widgets":{"b":{"type":"button","size":[60,20],"pos":[0,0],"font":"body","text":"10"},"s":{"type":"field","size":[52,18],"pos":[4,-81],"locked":1,"style":"plain","value":"10"},"a":{"type":"field","size":[53,20],"pos":[3,-57],"locked":1,"style":"plain"}}}}}

hi there! if i replaced everything after:

"on finish"

with:

"go[card]"

would i be able to have a project that automatically jumps the viewer to a new card once the countdown reaches 0? thank you very much for this program by the way, it's great!

Developer(+1)

Yes. Just to be clear, if you make an instance of the EggTimer contraption and edit it's script, it will have a template like

on finish do
end

You can then fill in an event handler for "finish" like you would any script. For example,

on finish do
 go["someCardName"]
end

Note that if the only thing you want to do is wait 10 seconds after clicking a button before navigating to another card you could use the sleep[] function on an ordinary button's script:

on click do
 sleep[10 * 60]
 go["someCardName"]
end

The difference being that sleep[] is "synchronous" and prevents the user from doing anything else while the script sleeps, whereas the EggTimer is "asynchronous" and the user could click something else or navigate away from the timer before it goes off.

ah, thank you for this. i'd rather use this asynchronous option because i aim to have the player click on elements of an image that either pop-up things or navigate to other cards while the countdown is still actively going on (not quite sure yet, but it's a work in progress).

Developer (1 edit) (+5)

Behold the beauty of

The eye contraption:

A configurable pupil image follows the pointer around the card, constrained by an oval inscribed within the contraption's bounding box. In most cases, you'll want this contraption to be shown in "transparent" mode, with the rest of the eyeball drawn on the card background, but there are also interesting possibilities for placing eyes behind partially-transparent canvases. (Note: make sure you upgrade to Decker v1.13 before using this contraption!)

To specify the pupil image, copy an image using the "select" tool and paste it into the "pupil image" rich-text field in the "Eye Properties" panel as shown above. You may want to use the "View -> Transparency Mask" setting to make parts of the pupil opaque white.

%%WGT0{"w":[{"name":"eye1","type":"contraption","size":[100,100],"pos":[88,220],"show":"transparent","def":"eye","widgets":{"pupil":{},"img":{}}}],"d":{"eye":{"name":"eye","size":[100,100],"resizable":1,"margin":[0,0,0,0],"description":"An animated eyeball that follows the user's pointer around the card. Best shown \"transparent\" with a blank eye underneath.","script":"on get_pupil do\n img.value\nend\n\non set_pupil x do\n img.value:x\n view[]\nend\n\non view do\n i:first extract arg where arg..type=\"image\" from img.value\n pupil.size:i.size\n pupil.clear[]\n pupil.paste[i 0,0 1]\n \n c:card.size/2\n p:pupil.size/2\n d:pointer.pos-card.offset+c\n m:(c-p)&mag d\n pupil.pos:(c-p)+m*unit heading d\nend","attributes":{"name":["pupil"],"label":["Pupil\nImage"],"type":["rich"]},"widgets":{"pupil":{"type":"canvas","size":[22,23],"pos":[110,44],"locked":1,"animated":1,"show":"transparent","border":0,"image":"%%IMG0ABYAFwAAAAH8AAf/AA//gB//wD//4H//8H//8P//+P//+P//+P//+P//+P//+P//+H//8H//8D//4B//wA//gAf/AAH8AAAAAA==","scale":1},"img":{"type":"field","size":[27,28],"pos":[-47,-56],"locked":1,"border":0,"value":{"text":["","i"],"font":["",""],"arg":["","%%IMG0ABYAFwAAAAH8AAf/AA//gB//wD//4H//8H//8P//+P//+P//+P//+P//+P//+P//+H//8H//8D//4B//wA//gAf/AAH8AAAAAA=="]}}}}}}
Developer (1 edit) (+4)

Hey kid, want a chicken? Chickens float. They all float when you're using

The "bob" Contraption:

Works much like the "eye" contraption: copy and paste a (probably transparent) image into the properties panel of the bob widget and your image will hover ominously above a shadow. Adjust the size of the contraption to control how vigorously the object bobs.

%%WGT0{"w":[{"name":"bob1","type":"contraption","size":[100,126],"pos":[305,100],"def":"bob","widgets":{"bg":{"size":[100,126],"image":"%%IMG2AGQAfgD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wDpAQ8ATwEbAEYBIQBBASUAPgEnAD0BJwA+ASUAQQEhAEcBGQBRAQ0A/wD/AOk="},"spr":{"size":[51,91],"pos":[24,20]},"img":{"value":{"text":["","i"],"font":["",""],"arg":["","%%IMG2ADMAWwDvIAQALyADAQIALSADAQEgAwAqIAYBAiABACggAwEBIAQBASACACYgBwEBIAIBASACAQEAJCADAQEgBQECIAIBASABACIgAwEBIAIBASAFAQEgAgEBIAEAIiAFAQEgAwECIAIBAiABAQEgAQAgIAEBASAFAQEgAgEBIAEBAiACAQEgAgAgIAQBAyABAQEgCAEBIAEAHyAEAQEgAwEBIAIBASABAQEgAQECIAIBAQAfIAEBASABAQIgAQECIAEBASABAQIgBAEBIAMAHiADAQIgAgEEIAIBBCAEAB0gAgECIAEBASACAQEgAwEBIAEBAyACACABASADAQEgAQECIAEBASABAQEgAQEBIAIBAgAgIAMBASACAQIgAgEDIAIBAyABAQEAHyABAQEgAgEFIAEBAyABAQEgAgECIAEAHwECIAEBAiABAQEgAgEEIAIBBCABAB8gAgEDIAEBCyABAQIAHgECIAEBBSABAQogAQEBAB4gAgEFIAEBASABAQogAQAeIAEBFAAeIAEBCCABAQYgAQEEAB0BCCABAQogAQECABsBASACAQMgAQEGIAEBCgAZAQEgAgECIAEBCiABAQMgAQEBIAIBAgAXAQEgAwEBIAEBAiABAQ0gAQEEIAEAFiAFAQEgAQETIAIBAQAVIAcBBSABARAgAQAVIAEBASABAQEgAwEBIAEBFgATIAEBASABAQIgAwEBIAEBASABAQggAQELABIgAwECIAIBAiABAQEgAgEDIAEBEQAQAQEgAgEBIAEBASACAQMgAQEKIAEBDAAPAQEgAQECIAEBASABAQIgAgEEIAEBASACAQIgAQEOAA8BASACAQIgAQEBIAEBASABAQMgAQEHIAEBByABAQYgAQAOIAEBAyABAQEgAQEEIAEBAiABAQogAQELIAEADQEBIAEBASADAQEgAgEHIAEBFCABAA0gAQEBIAEBASABAQIgAgEdIAEADAEDIAEBASABAQMgAQEBIAEBGSABAQEADAEDIAIBASABAQcgAQEWIAEBAgALAQEgAgEjIAEBAQALAQwgAQEaAAwBJwAMARkgAQEMAA0BJgANAQEgAQEkAAwBBCABASIACwEDIAEBIwAMIAEBJSABAAwBASACASQACwECIAIBIwAMAScADAECIAEBJAAMIAEBAiABASMADAEVIAEBEAANASYADQEFIAEBIAAOASUADgEUIAEBDwAQASMAEQEhABMgAQEQIAEBDQAVIAMBCyABAQ8AGQEYABsgAQEWAB0gAQETACABDiABAQMAISABAQIAAiABAQUABQECACEgAQECAA0BAwAgIAEBAgAOAQIAIQEDAA0gAQECACAgAQECAA0gAQECACAgAQECAA0gAQEDAB4BBQAMIAEBBAAcIAEBBgAKAQcAGyABAQMgAQEDAAggAQEIABoBAwACIAEBAgAHIAEBAyABAQYAGQEDAAIgAQEDAAYgAQEDAAEBAyABAQMAGAECAAQBAwAGIAEBAgACAQIAAiABAQIAGCABAAUBAgAHIAEBAgACAQIAAwECAB4BAgAIIAEAAiABAQIAAwECAB4BAgAMAQIAAwECAB8BAQALIAEBAgAkAQEACyABAQIAMCABAQEAMSACABA="]}}}}],"d":{"bob":{"name":"bob","size":[100,100],"resizable":1,"margin":[5,5,5,5],"description":"Animate an object bobbing in midair with a shadow.","script":"on get_object do img.value end\non set_object x do img.value:x view[] end\n\non oval pos size do\n flip pos+size*flip unit 2*pi*(range 20)/20\nend\n\non view do\n i:first extract arg where arg..type=\"image\" from get_object[]\n spr.size:i.size\n spr.clear[]\n spr.paste[i]\n \n bh:.9*bg.size[1]\n sh:spr.size[1]\n sy:.5*bh-sh\n t:(card.index)+sys.ms*0.002\n spr.pos:(.5*bg.size[0]-spr.size[0]),sy+sy*sin t\n\n bg.clear[]\n bg.poly[oval[bg.size*.5,.9 (.4+.2*1+sin t)*(.5*spr.size[0]),.05*bg.size[1]]]\nend","attributes":{"name":["object"],"label":["Object"],"type":["rich"]},"widgets":{"bg":{"type":"canvas","size":[100,100],"pos":[0,0],"locked":1,"show":"transparent","border":0,"scale":1},"spr":{"type":"canvas","size":[23,23],"pos":[38,63],"locked":1,"animated":1,"show":"transparent","border":0,"scale":1},"img":{"type":"field","size":[26,23],"pos":[0,-46],"locked":1,"value":{"text":["","i"],"font":["",""],"arg":["","%%IMG2ABcAFwBIAREABgEBDQ8BAQAGAQENDwEBAAYBAQ0PAQEABgEBDQ8BAQAGAQENDwEBAAYBAQ0PAQEABgEBDQ8BAQAGAQENDwEBAAYBAQ0PAQEABgEBDQ8BAQAGAQENDwEBAAYBAQ0PAQEABgEBDQ8BAQAGAQENDwEBAAYBAQ0PAQEABgERAEg="]}}}}}}
Developer (1 edit) (+7)

The v1.14 release introduced a generalization for read[], allowing scripts to break an animated GIF image down into frames. There are many interesting ways to take advantage of this functionality. For example,

The "gif" Contraption:

At last, an easy way to import animated GIFs into decks! This contraption prompts the user to select a GIF file, unpacks and dithers it to 1-bit, automatically resizes itself to match the size of the image, and then loops the frames at 30fps:

Note that using GIF widgets can quickly expand the size of your decks- use them sparingly, and avoid importing huge or overly-long animations! Decker's GIF loader is brand-new and may have some quirks to hammer out, so remember to save often while playing with this feature.

%%WGT0{"w":[{"name":"gif1","type":"contraption","size":[100,100],"pos":[206,121],"def":"gif","widgets":{"c":{},"f":{},"b":{}}}],"d":{"gif":{"name":"gif","size":[100,100],"resizable":1,"margin":[0,0,0,0],"description":"Import and play animated gifs. Careful: huge gifs can quickly bloat the size of your deck!","script":"on view do\n fr:extract arg where arg..type=\"image\" from f.value\n c.show:card.show\n c.clear[]\n if count fr\n  b.show:\"none\"\n  i:fr[(count fr)%sys.ms/2*60]\n  card.size:i.size\n  c.paste[i]\n else\n  b.show:\"solid\"\n end\nend","widgets":{"c":{"type":"canvas","size":[100,100],"pos":[0,0],"locked":1,"animated":1,"border":0,"scale":1},"f":{"type":"field","size":[73,35],"pos":[8,-50],"locked":1},"b":{"type":"button","size":[69,20],"pos":[16,40],"script":"on click do\n g:\"[255,0,255,246,148,108,132,51,61,147,144,86,66,111,185,134,69,0]\"\n grays:(0,1,32+range 16) dict \"%j\" parse g\n f.value:raze each i in read[\"image\" \"frames\"].frames\n  rtext.make[\"\" \"\" i.map[grays].transform[\"dither\"]]\n end\n view[]\nend","text":"Open Gif..."}}}}}

The "scrubber" Contraption:

This contraption doesn't animate automatically; instead, it allows a user to manually scrub back and forth through the frames of the animation. It also demonstrates importing color images (resampled to the Decker 16-color palette) instead of 1-bit dithering.

%%WGT0{"w":[{"name":"scrubber1","type":"contraption","size":[100,100],"pos":[49,108],"def":"scrubber","widgets":{"c":{},"f":{},"b":{},"s":{}}}],"d":{"scrubber":{"name":"scrubber","size":[100,100],"resizable":1,"margin":[0,0,0,19],"description":"Import animated gifs and allow the user to manually scrub through the frames. Careful: huge gifs can quickly bloat the size of your deck!","script":"on change do\n view[]\nend\n\non view do\n fr:extract arg where arg..type=\"image\" from f.value\n s.interval:0,(count fr)-1\n c.show:card.show\n c.clear[]\n if count fr\n  b.show:\"none\"\n  i:fr[s.value]\n  card.size:i.size+s.size*0,1\n  c.paste[i]\n else\n  b.show:\"solid\"\n end\nend","widgets":{"c":{"type":"canvas","size":[100,100],"pos":[0,0],"locked":1,"border":1,"scale":1},"f":{"type":"field","size":[73,35],"pos":[8,-50],"locked":1},"b":{"type":"button","size":[69,20],"pos":[16,40],"script":"on click do\n f.value:raze each i in read[\"image\" \"frames\"].frames\n  rtext.make[\"\" \"\" i]\n end\n view[]\nend","text":"Open Gif..."},"s":{"type":"slider","size":[100,17],"pos":[0,83],"interval":[-1,0]}}}}}

There are many variations possible on the above ideas that you might want, like looping back-and-forth, rescaling imported images to fit the bounding box of the contraption, or firing events to drive other animations. Feel free to share your own takes!

(+1)

Hello, I've been poking around in the wonders of decker recently and reading up as much as I can, but I couldn't figure out how to use this contraption for colored gifs. Is there a way, and if so, do you think you could help? Thanks!

Developer (1 edit) (+1)

The "gif" contraption is designed to read gifs as a series of grayscale frames and dither them.

We could make it handle color by replacing that dithering code, which is in the script of the "b" button,

 g:"[255,0,255,246,148,108,132,51,61,147,144,86,66,111,185,134,69,0]"
 grays:(0,1,32+range 16) dict "%j" parse g
 f.value:raze each i in read["image" "frames"].frames
  rtext.make["" "" i.map[grays].transform["dither"]]
 end

With something based on the equivalent in the "scrubber" contraption:

 f.value:raze each i in read["image" "frames"].frames
  rtext.make["" "" i]
 end

All together as a copyable snippet:

%%WGT0{"w":[{"name":"gif1","type":"contraption","size":[100,100],"pos":[206,121],"def":"colorgif","widgets":{"c":{},"f":{},"b":{}}}],"d":{"colorgif":{"name":"colorgif","size":[100,100],"resizable":1,"margin":[0,0,0,0],"description":"Import and play animated 16-color gifs. Careful: huge gifs can quickly bloat the size of your deck!","script":"on view do\n fr:extract arg where arg..type=\"image\" from f.value\n c.show:card.show\n c.clear[]\n if count fr\n  b.show:\"none\"\n  i:fr[(count fr)%sys.ms/2*60]\n  card.size:i.size\n  c.paste[i]\n else\n  b.show:\"solid\"\n end\nend","widgets":{"c":{"type":"canvas","size":[100,100],"pos":[0,0],"locked":1,"animated":1,"border":0,"scale":1},"f":{"type":"field","size":[73,35],"pos":[8,-50],"locked":1},"b":{"type":"button","size":[69,20],"pos":[16,40],"script":"on click do\n f.value:raze each i in read[\"image\" \"frames\"].frames\n  rtext.make[\"\" \"\" i]\n end\n view[]\nend","text":"Open Gif..."}}}}}

Note that this is still ultimately limited by Decker's 16-color palette; colors not in the palette are converted into the current palette via a minimum-squared-distance heuristic, which doesn't always do a fantastic job.


For best results you might need to customize Decker's palette and/or use external tools like imagemagick to adjust the GIF's palette. If you just want a little motion and color in your decks, you could instead directly import the "wiggler" contraption from wigglypaint.

Does any of that help point you in the right direction?

(+1)

Oh this should be perfect, yes, thank you! I had already read up on the limited palette and spent a few good hours figuring imagemagick out to adjust gifs, so that should be easy enough now. Thank you kindly!

Developer(+3)

A nice, simple one this time-

The "enum" Contraption:


Works just like a "compact" slider widget, but instead of a numeric value within a range, it chooses a string from a list of newline-delimited "options". This widget respects the "font", "show", and "locked" properties, and produces a "change[]" event just like a slider.

%%WGT0{"w":[{"name":"enum1","type":"contraption","size":[100,25],"pos":[289,76],"script":"on change val do\n \nend","def":"enum","widgets":{"s":{"interval":[0,2],"format":"one"},"o":{"value":"one\ntwo\nthree"}}}],"d":{"enum":{"name":"enum","size":[100,25],"resizable":1,"margin":[5,5,5,5],"description":"select from among enumerated string values.","script":"on get_options   do o.text end\non get_value     do (\"\\n\" split o.text)[s.value] end\non set_options x do o.text:x s.value:0 view[] end\n\non set_value x do\n v:\"\\n\" split o.text\n d:v dict range count v\n s.interval:0,(count v)-1\n s.value:v[0] unless d[x]\n s.format:v[s.value]\n s.font:card.font\n s.show:card.show\n s.locked:card.locked\nend\n\non change do\n view[]\n card.event[\"change\" get_value[]]\nend\n\non view do\n v:\"\\n\" split o.text\n s.interval:0,(count v)-1\n s.format:v[s.value]\n s.font:card.font\n s.show:card.show\n s.locked:card.locked\nend\n","template":"on change val do\n \nend","attributes":{"name":["options"],"label":["Options"],"type":["code"]},"widgets":{"s":{"type":"slider","size":[100,25],"pos":[0,0],"interval":[0,100],"style":"compact"},"o":{"type":"field","size":[100,20],"pos":[0,-30],"locked":1,"style":"plain"}}}}}
Developer

A handy variation on the "enum",

The imageEnum Contraption:

Load this contraption with a selection of images, and then pick between them. Exposes attributes "value" (an Image interface, read-only!), "imageIndex" (an integer, read/write), respects the standard "show" and "locked" attributes, and provides a "change[]" event, just like enums and the slider.

%%WGT0{"w":[{"name":"imageEnum1","type":"contraption","size":[63,34],"pos":[229,154],"def":"imageEnum","widgets":{"s":{"size":[63,34]},"c":{"size":[33,30],"image":"%%IMG2ACEAHgD/AP8A/wDh"},"o":{"size":[44,31],"pos":[92,-20]}}}],"d":{"imageEnum":{"name":"imageEnum","size":[100,100],"resizable":1,"margin":[17,3,18,5],"description":"select from among enumerated image values.","script":"on images do\n extract arg where arg..type=\"image\" from o.value\nend\n\non get_options do o.value end\non set_options x do o.value:x view[] x end\non get_value do images[][s.value] end\non get_valueIndex do s.value end\non set_valueIndex x do s.value:x view[] x end\n\non view do\n im:images[]\n i:im[s.value]\n s.interval:0,0|(count im)-1\n s.locked:card.locked\n s.show  :card.show\n c.show  :card.show\n c.clear[]\n c.paste[i .5*c.size-i.size 1]\nend\n\non change do\n card.event[\"change\" get_value[] s.value]\n view[]\nend","template":"on change img index do\n \nend","attributes":{"name":["options"],"label":["Options"],"type":["rich"]},"widgets":{"s":{"type":"slider","size":[100,100],"pos":[0,0],"interval":[0,0],"format":"","style":"compact"},"c":{"type":"canvas","size":[70,96],"pos":[15,2],"locked":1,"border":0,"image":"%%IMG2AEYAYAD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8AWg==","scale":1},"o":{"type":"field","size":[44,53],"pos":[129,-20],"locked":1,"show":"none"}}}}}
(+1)

is there a way to have a kind of loop, so that when you click on the right arrow when you're at the last text, it goes back to the first text, and vice versa?

Developer(+1)

seeking affirmation? look no further than the

Validator Contraption:

Works much like a field, but will display informative (or annoying) text below if the value doesn't pass muster.

The validation rule(s) are provided in the script of the contraption instance, by filling out the "on check" function. This function is given the current text, and may return either an error message (a string) or the number 0 if the text is valid. In the above example, the validation function looks like so:


%%WGT0{"w":[{"name":"validator1","type":"contraption","size":[128,41],"pos":[173,242],"def":"validator","widgets":{"v":{},"e":{}}}],"d":{"validator":{"name":"validator","size":[128,41],"resizable":1,"margin":[89,5,6,22],"description":"a text input field with configurable validation rules.","script":"on is_valid  x do 0~me.event[\"check\" x] end\non get_valid   do is_valid[v.text] end\non get_text    do if is_valid[v.text] v.text else 0 end end\non set_text  x do if is_valid[x] v.text:x end end\n\non change x silent do\n err:card.event[\"check\" v.text]\n e.text:if 0~err\n  if !silent card.event[\"change\" x] end\n  \"\"\n else\n  err\n end\nend\n\non view do\n v.font  :card.font\n v.locked:card.locked\n v.show  :card.show\n e.show  :card.show\n change[v.text 1]\nend","template":"on change x do\n \nend\n\non check x do\n \nend","attributes":{"name":["text"],"label":["Text"],"type":["string"]},"widgets":{"v":{"type":"field","size":[120,17],"pos":[4,4]},"e":{"type":"field","size":[120,14],"pos":[4,23],"locked":1,"border":0,"style":"plain"}}}}}

I'm super new to Decker and trying to use the validator contraption as a way to implement a specific password in order to be able to advance to the next card. I'm not sure if that is the intended function of this contraption or a possibility and would greatly appreciate any guidance.

Developer

It would probably be just as easy to use a plain field as a Validator for what you describe. Say you have a field named "myfield". You could then have a button with a script something like:

on click do
 if myfield.text ~ "the password"
  go[someOtherCard]
 end
end

Or perhaps you could give the field a script and enable a button when it contains the correct text?

on change val do
 myButton.locked: !val~"the password"
end

This thread has some additional discussion about implementing a password/search system that might be useful.

(+1)

Thanks for the quick feedback! I shall try to implement both of those solutions, if only to get a better grasp on the system and figure out which one works best. I will also check out the linked thread. Thanks again!

Developer (1 edit) (+2)

Time zones are a drag. Banish them with

The .Beat Contraption:


A nice simple one. Display the current Swatch Internet Time.

%%WGT0{"w":[{"name":".beat1","type":"contraption","size":[96,28],"def":".beat","widgets":{"t":{"value":"@ 668.75"}}}],"d":{".beat":{"name":".beat","size":[96,28],"margin":[0,0,0,0],"description":"display the .beat time, also known as Swatch Internet Time.","image":"%%IMG2AGAAHAACAVwAAwECIFoBAgABAQIgAQFaIAEBAyABAVwgAQECIAEBXCABAQIgAQFcIAEBAiABAVwgAQECIAEBBiACAVQgAQECIAEBBiACAVQgAQECIAEBBiACAVQgAQECIAEBBiACARQgAgE+IAEBAiABAQYgAgEUIAIBPiABAQIgAQEGIAUBAyAEAQMgAwEBIAEBASAEAT0gAQECIAEBBiAGAQEgBgEBIAYBASAEAT0gAQECIAEBBiACAQIgAgEBIAIBAiACAQEgAgECIAIBAiACAT4gAQECIAEBBiACAQIgAgEBIAIBAiACAQEgAgECIAIBAiACAT4gAQECIAEBBiACAQIgAgEBIAYBASACAQIgAgECIAIBPiABAQIgAQEGIAIBAiACAQEgAgEFIAIBAiACAQIgAgE+IAEBAiABAQYgBgEBIAYBASAGAQIgAgE+IAEBAiABAQQgAQEBIAUBAyAEAQMgAwEBIAEBAyACAT0gAQECIAEBXCABAQIgAQFcIAEBAiABAVwgAQECIAEBXCABAQIgAQFcIAEBAyABAVogAQECAAEBAiBaAQIAAwFcAAI=","widgets":{"t":{"type":"field","size":[54,15],"pos":[39,7],"locked":1,"animated":1,"script":"on view do\n p:\"%p\" parse \"%e\" format sys.now\n b:(1/86.4)*(3600*24%p.hour+1)+(60*p.minute)+p.second\n me.text:\"@ %4.2f\" format b\nend","font":"mono","show":"invert","border":0,"style":"plain"}}}}}

No configuration options for this one. It might be a fun exercise to try creating an enhanced version that fires an event when the .beat advances!

Developer (2 edits) (+2)

Tired of inert masses? Why not bring them to life with

The Pulse Contraption

Turn your favorite image into a pulsating mass! Works much like the Eye Contraption; copy and paste an image of your choice into an instance of the Pulse Contraption and it will begin to writhe and stretch, scaling from its initial size to the bounds of the contraption. It is also possible to adjust the speed and whether the axes scale in sync, or offset with one another.

%%WGT0{"w":[{"name":"Pulse1","type":"contraption","size":[100,100],"pos":[49,189],"def":"Pulse","widgets":{"c":{},"o":{},"i":{},"s":{}}}],"d":{"Pulse":{"name":"Pulse","size":[100,100],"resizable":1,"margin":[5,5,5,5],"description":"A quivering, pulsating mass.","script":"on get_sprite   do i.value   end\non set_sprite x do i.value:x end\non get_axes     do o.value   end\non set_axes   x do o.value:x end\non get_speed    do s.text+0  end\non set_speed  x do s.text:x  end\n\non view do\n img:first extract arg where arg..type=\"image\" from i.value\n scl:.5*1+sin(o.value*pi*0,1)+(.02*s.text)*sys.frame\n size:img.size+scl*c.size-img.size\n pos:.5*c.size-size\n c.clear[]\n c.paste[img pos,size 1]\nend","attributes":{"name":["sprite","axes","speed"],"label":["Image","Offset Axes?","Speed"],"type":["rich","bool","number"]},"widgets":{"c":{"type":"canvas","size":[100,100],"pos":[0,0],"locked":1,"animated":1,"show":"transparent","border":0,"scale":1},"o":{"type":"button","size":[99,20],"pos":[131,-30],"locked":1,"show":"none","text":"Offset Axes?","style":"check","value":0},"i":{"type":"field","size":[100,20],"pos":[130,-1],"locked":1,"show":"none","value":{"text":["","i"],"font":["",""],"arg":["","%%IMG2ABIAEgAGAQYACgEKAAcBDAAFAQUNBAEFAAMBBA0IAQQAAgEDDQoBAwABAQQNCgEHDQwBBg0MAQYNDAEGDQwBBw0KAQQAAQEDDQoBAwACAQQNCAEEAAMBBQ0EAQUABQEMAAcBCgAKAQYABg=="]}},"s":{"type":"field","size":[100,20],"pos":[130,28],"locked":1,"show":"none","style":"plain","value":"1"}}}}}

Note: large pulsating images may cause Web-Decker to bog down on slower machines, and line drawings generally produce better-looking results than dithered images.

Developer (1 edit) (+1)

Make your deck teem with activity using

The Turtle Contraption:


Just like its namesake animal, the Turtle Contraption is a little triangular critter that moves around. If you drag them around manually you can reposition them on the card.

The behavior of each turtle is controlled by their script. The "move" function is given a dictionary containing the turtle's "pos" (position) and "dir" (direction). This function should return a modified dictionary containing a new position and direction, and can optionally set "pat" (a pattern number for drawing the turtle in) and "fd" (a number of pixels to move forward in the current direction). From these building blocks, many interesting behaviors can be built, as demonstrated above:


The definition below is pre-loaded with the last of the above behaviors, which is perhaps the most interesting: it wanders around and avoids any "walls" that have been drawn on the card background. These turtles aren't extremely good at solving mazes, but perhaps you could improve their performance?

%%WGT0{"w":[{"name":"flatlander","type":"contraption","size":[30,30],"pos":[219,59],"script":"on move x do\n d:0 while (d<30)&!card.image[floor x.pos+d*unit x.dir] d:d+1 end\n x.dir:x.dir+if d<20 .01 else .001 end*random[100]-50\n x.fd :d>10\n x.pat:8+d<10\nend","show":"transparent","def":"turtle","widgets":{"c":{},"s":{}}}],"d":{"turtle":{"name":"turtle","size":[30,30],"margin":[0,0,0,0],"description":"a programmable embodied agent.","script":"on get_state   do s.text   end\non set_state x do s.text:x end\n\non view do\n # state management\n os:\"%j\" parse s.text\n offset:card.size/2\n center:card.pos+offset\n is.pos :os.fp+center\n is.dir :os.dir\n is:card.event[\"move\" is]\n c.pattern:os.pat:32 unless is.pat\n if \"fd\" in is is.pos:is.pos+is.fd*unit is.dir end\n os.dir :is.dir\n os.fp  :is.pos-floor is.pos\n card.pos:(512,342)%is.pos-offset\n s.text:\"%j\" format os\n \n # rendering\n tri:4 take flip offset+flip(14,10,10)*unit os.dir+(.666*pi)*range 3\n c.clear[]\n c.poly[tri]\n c.pattern:1\n c.line[tri]\nend","template":"on move x do\n x\nend","attributes":{"name":["state"],"label":["State"],"type":["code"]},"widgets":{"c":{"type":"canvas","size":[30,30],"pos":[0,0],"locked":1,"animated":1,"script":"on drag do\n card.pos:pointer.pos-me.size/2 \n me.pos:0\nend","show":"transparent","border":0,"draggable":1,"scale":1},"s":{"type":"field","size":[100,20],"pos":[57,-4],"show":"none"}}}}}
(8 edits) (+3)

JankyTunes: The Contraption


Do you want to play back cute little chiptune songs in your Decker decks? Building on my Janky Sequencer project, I've refactored that code into a contraption that you can use to play tunes you write in it inside your own decks!

You can grab the code for the contraption from inside the deck on the project page along with the instructions and some example stuff: https://micpp.itch.io/jankytunes-the-contraption

Or grab it from below (now updated to Version 4.1)!

%%WGT0{"w":[{"name":"jankytunes1","type":"contraption","size":[193,72],"pos":[159,135],"def":"jankytunes","widgets":{"notes":{},"pitches":{},"synthparams":{},"button1":{},"playbegin":{},"playstop":{},"loop":{},"playing":{},"lastframe":{},"buffer1":{},"buffer2":{},"buffer3":{},"buffer4":{}}}],"d":{"jankytunes":{"name":"jankytunes","size":[193,72],"margin":[0,0,0,0],"description":"a player for jankysequencer format music","version":4.1,"script":"on view do\n if count notes.value\n  button1.show:\"none\"\n  button1.locked:1\n  playbegin.show:\"solid\"\n  playbegin.locked:0\n  playstop.show:\"solid\"\n  playstop.locked:0\n  loop.show:\"solid\"\n  loop.locked:0\n else\n  button1.show:\"solid\"\n  button1.locked:0\n end\nend\n\non get_animate do\n animate\nend\n\n#this is called by the view handler on the frame counter\non animate do\n if playing.value\n  tempo:(first extract value where param=\"tempo\" from synthparams.value)+0\n  if !((lastframe.value[0].text+tempo)>sys.frame)\n   lastframe.value:sys.frame\n   #play the current row from buffer\n   if notes.row = ((count notes.value)-1)\n    if loop.value\n     notes.row:0\n     notes.scroll:0\n     playbuffer[]\n     card.event[\"playline\" notes.row]\n     calcnextrow[]\n    else\n     stopplayback[]\n     card.event[\"reachedend\"]\n    end\n   else\n    notes.row:notes.row+1\n    notes.scroll:notes.row\n    playbuffer[]\n    card.event[\"playline\" notes.row]\n    calcnextrow[]\n   end\n  end\n end\nend\n\non get_row do\n notes.row\nend\n\non set_row x do\n notes.row:x\nend\n\non get_loop do\n loop.value\nend\n\non set_loop x do\n loop.value:x\nend\n\non get_playfromstart do\n playfromstart\nend\n\non get_play do\n startplayback\nend\n\non get_stop do\n stopplayback\nend\n\non get_isplaying do\n playing.value\nend\n\non playfromstart do\n notes.row:0\n startplayback[]\nend\n\n\n\non startplayback do\n playstop.text:\"Stop\"\n #calculate row into buffer\n calcrow[]\n lastframe.value:sys.frame\n playbuffer[]\n card.event[\"playline\" notes.row]\n #calc next row\n calcnextrow[]\n playing.value:1\nend\n\non stopplayback do\n playing.value:0\n playstop.text:\"Play\"\n card.event[\"playstopped\"]\nend\n\non calcnextrow do\n if notes.row = ((count notes.value)-1)\n  notes.row:0\n  calcrow[]\n  notes.row:((count notes.value)-1)\n else\n  notes.row:notes.row+1\n  calcrow[]\n  notes.row:notes.row-1\n end\nend\n\non calcrow do\n #voice1\n if !(notes.rowvalue[\"Voice1\"]=\"\")\n  buffer1.value:calcnote[notes.rowvalue[\"Voice1\"]\n   notes.rowvalue[\"Len1\"] 1].encoded\n else buffer1.value:\"\"\n end\n #voice2\n if !(notes.rowvalue[\"Voice2\"]=\"\")\n  buffer2.value:calcnote[notes.rowvalue[\"Voice2\"]\n   notes.rowvalue[\"Len2\"] 2].encoded\n else buffer2.value:\"\"\n end\n #voice3\n if !(notes.rowvalue[\"Voice3\"]=\"\")\n  buffer3.value:calcnote[notes.rowvalue[\"Voice3\"]\n   notes.rowvalue[\"Len3\"] 3].encoded\n else buffer3.value:\"\"\n end\n #noise\n if !(notes.rowvalue[\"Noise\"]=\"\")\n  buffer4.value:calcnoise[notes.rowvalue[\"Noise\"]].encoded\n else buffer4.value:\"\"\n end\nend\n\non playbuffer do\n play[sound[buffer1.value[0].text]]\n play[sound[buffer2.value[0].text]]\n play[sound[buffer3.value[0].text]]\n play[sound[buffer4.value[0].text]]\nend\n\non calcnoise beat do\n #calculate length\n tempo:(first extract value where param=\"tempo\" from synthparams.value)+0\n length:(tempo/60)*8000*beat\n \n  v:(first extract value where param=\"volume4\" from synthparams.value)+0\n  a:(first extract value where param=\"attack4\" from synthparams.value)+0\n  d:(first extract value where param=\"decay4\" from synthparams.value)+0\n  s:(first extract value where param=\"sustain4\" from synthparams.value)+0\n  r:(first extract value where param=\"release4\" from synthparams.value)+0\n  \n  l:(length-a)-d\n  if l<0 l:0 end\n  x:range (length+r)\n  \n  noise:random[((range 2*v)-v) length+r]\n  \n  sound[envelope[x a d s/100 r l 1]*noise]\nend\n\non calcnote note beat voice do\n #calculate pitch\n if (count note) = 3\n  octave: note[2]\n  basenote: \"\" fuse (note[0], note[1])\n else\n  octave: note[1]\n  basenote: note[0]\n end\n basepitch: (first extract pitch where note=basenote from pitches.value)+0\n pitch:basepitch*2^(octave-4)\n \n #calculate length\n tempo:(first extract value where param=\"tempo\" from synthparams.value)+0\n length:(tempo/60)*8000*beat\n \n #generate waveform + envelope\n \n if voice=1\n  wavetype:first extract value where param=\"wavetype1\" from synthparams.value\n  v:(first extract value where param=\"volume1\" from synthparams.value)+0\n  a:(first extract value where param=\"attack1\" from synthparams.value)+0\n  d:(first extract value where param=\"decay1\" from synthparams.value)+0\n  s:(first extract value where param=\"sustain1\" from synthparams.value)+0\n  r:(first extract value where param=\"release1\" from synthparams.value)+0\n elseif voice=2\n  wavetype:first extract value where param=\"wavetype2\" from synthparams.value\n  v:(first extract value where param=\"volume2\" from synthparams.value)+0\n  a:(first extract value where param=\"attack2\" from synthparams.value)+0\n  d:(first extract value where param=\"decay2\" from synthparams.value)+0\n  s:(first extract value where param=\"sustain2\" from synthparams.value)+0\n  r:(first extract value where param=\"release2\" from synthparams.value)+0\n else\n  wavetype:first extract value where param=\"wavetype3\" from synthparams.value\n  v:(first extract value where param=\"volume3\" from synthparams.value)+0\n  a:(first extract value where param=\"attack3\" from synthparams.value)+0\n  d:(first extract value where param=\"decay3\" from synthparams.value)+0\n  s:(first extract value where param=\"sustain3\" from synthparams.value)+0\n  r:(first extract value where param=\"release3\" from synthparams.value)+0\n end\n \n l:(length-a)-d\n if l<0 l:0 end\n \n if wavetype=\"square\"\n  wave:squarewave\n elseif wavetype=\"sawtooth\"\n  wave:sawwave\n else\n  wave:sinewave\n end\n \n x:range (length+r)\n \n sound[envelope[x a d s/100 r l v]*wave[pitch x]]\n\nend\n\non sinewave pitch x do\n sin (pitch/8000)*2*pi*x\nend\n\non squarewave pitch x do\n sine:sinewave[pitch x]\n (sine>0) - (sine<0) - (sine=0)\nend\n\non sawwave pitch x do\n l:8000/pitch\n floatmod:((l*10000)%(x*10000))/10000\n (((floatmod) / l)*2)-1\nend\n\non envelope x a d s r l v do\n ((x<a) * ((v/a)*x)) + #attack\n ((x=a)*v) +\n (((a<x) & (x<(a+d))) * ((((v*(s-1))/d)*x) + v -(((v*(s-1))/d)*a))) + #decay\n ((x=(a+d)) * (s*v)) +\n ((((a+d)<x) & (x<(a+d+l))) * (s*v)) + #sustain\n ((x=(a+d+l)) * (s*v)) +\n ((((a+d+l)<x) & (x<(a+d+l+r))) * ((((-s*v)/r)*x) + ((s*v)/r)*(a+d+l+r))) #release\nend","template":"on playstopped do\n\nend\n\non reachedend do\n\nend\n\non playline row do\n\nend","attributes":{"name":["loop","row"],"label":["Looping?","Current row"],"type":["bool","number"]},"widgets":{"notes":{"type":"grid","size":[100,50],"pos":[226,-62],"locked":1,"value":{},"row":20},"pitches":{"type":"grid","size":[100,50],"pos":[237,38],"locked":1,"value":{}},"synthparams":{"type":"grid","size":[100,50],"pos":[237,132],"locked":1,"value":{}},"button1":{"type":"button","size":[72,20],"pos":[59,28],"script":"on click do\n alert[\"Select the notes CSV\"]\n notes.value:readcsv[read[\"text\"]]\n alert[\"Select the synth parameters CSV\"]\n synthparams.value:readcsv[read[\"text\"]]\n alert[\"Select the pitches CSV\"]\n pitches.value:readcsv[read[\"text\"]]\n me.show:\"none\"\n me.locked:1\n notes.row:0\n view[]\nend","text":"Load song"},"playbegin":{"type":"button","size":[102,20],"pos":[10,7],"locked":1,"script":"on click do\n playfromstart[]\nend","show":"none","text":"Play from start"},"playstop":{"type":"button","size":[60,20],"pos":[120,7],"locked":1,"script":"on click do\n if !playing.value\n  startplayback[]\n else\n  stopplayback[]\n end\nend","show":"none","text":"Play"},"loop":{"type":"button","size":[60,20],"pos":[66,44],"locked":1,"show":"none","text":"Loop","style":"check","value":0},"playing":{"type":"button","size":[68,20],"pos":[115,147],"locked":1,"text":"playing","style":"check","value":0},"lastframe":{"type":"field","size":[100,20],"pos":[-42,141],"locked":1,"animated":1,"script":"on change val do\n \nend\n\non view do\n animate[]\nend","value":"0"},"buffer1":{"type":"field","size":[100,20],"pos":[-139,-105],"locked":1},"buffer2":{"type":"field","size":[100,20],"pos":[-15,-105],"locked":1},"buffer3":{"type":"field","size":[100,20],"pos":[110,-106],"locked":1},"buffer4":{"type":"field","size":[100,20],"pos":[232,-104],"locked":1}}}}}

If you use this in a deck, I'd appreciate if you gave me credit - also please let me know if you do as I'd love to see what you can come up with!

(+3)

wow! I came here today to say how impressed I was by your July jam musical decks. This contraption is next-level!

(+1)

Updated this contraption to work with the changes in Decker 1.32

(+1)

And another update to version 4, that adds a few new features

Developer (1 edit) (+4)

The following contraptions demonstrate Decker 1.25: contraptions can now be tools to help manipulate and organize the contents of decks. Be sure to upgrade before giving these a spin!

The decaying web sure is a bummer. Turn your gaze inward with

The SearchEngine Contraption:

Type a short phrase into the searchEngine textbox and it will scour your deck for any cards or widgets containing it (case-insensitive), producing a list of clickable links to the cards it finds.

  • If any card contains a widget named "noindex", it will be skipped.
  • The searchEngine considers the .text attribute of every widget. If you're using contraptions with content you want searched, expose it via a custom attribute!
  • The searchEngine also considers invisible widgets; you can add hidden fields to supplement the visible content of cards.
%%WGT0{"w":[{"name":"deckDeckGo","type":"contraption","size":[209,81],"pos":[149,239],"def":"searchEngine","widgets":{"i":{},"o":{"show":"none"}}}],"d":{"searchEngine":{"name":"searchEngine","size":[209,81],"resizable":1,"margin":[5,29,21,6],"description":"search the contents of your deck and easily navigate to cards with your search keyword(s).","script":"on change do\n s:\"%l\" format i.text\n r:0 take rtext.make[]\n if count s\n  each c k in deck.cards\n   if !\"noindex\" in c.widgets\n    f:s in \"%l\" format k\n    each w in c.widgets\n     f:f|s in \"%l\" format w.text\n    end\n    if f\n     r:r,rtext.make[k \"\" k],rtext.make[\"\\n\"]\n    end\n   end\n  end\n end\n if count r\n  o.show:\"solid\"\n  o.value:r\n else\n  o.show:\"none\"\n end\nend\n\non view do\n change[]\nend","widgets":{"i":{"type":"field","size":[203,20],"pos":[3,3],"style":"plain"},"o":{"type":"field","size":[203,51],"pos":[3,27],"locked":1,"scrollbar":1}}}}}

Want a more compact alternative to the pattern editor tool? Why not try

The PatEdit Contraption:

Scroll through any of Decker's 1-bit patterns, make changes, and watch the results live. When you're satisfied with the changes, you can even delete this contraption entirely!

%%WGT0{"w":[{"name":"patEdit","type":"contraption","size":[68,86],"pos":[29,38],"def":"patEdit","widgets":{"c":{},"p":{}}}],"d":{"patEdit":{"name":"patEdit","size":[68,86],"margin":[0,0,0,0],"description":"a mini pattern editor tool.","script":"\non edit do\n patterns[p.value]:c.copy[]\nend\n\non click pos do\n me.pattern:!me[pos]\n edit[]\nend\n\non drag pos do\n send drag[pos]\n edit[]\nend\n\non release pos do\n drag[pos]\nend\n\non change do\n c.paste[patterns[p.value]]\nend\n\non view do\n change[]\nend","image":"%%IMG2AEQAVgACAUAAAwEBIEABAQABAQEgQgECIEIBAiBCAQIgQgECIEIBAiBCAQIgQgECIEIBAiBCAQIgQgECIEIBAiBCAQIgQgECIEIBAiBCAQIgQgECIEIBAiBCAQIgQgECIEIBAiBCAQIgQgECIEIBAiBCAQIgQgECIEIBAiBCAQIgQgECIEIBAiBCAQIgQgECIEIBAiBCAQIgQgECIEIBAiBCAQIgQgECIEIBAiBCAQIgQgECIEIBAiBCAQIgQgECIEIBAiBCAQIgQgECIEIBAiBCAQIgQgECIEIBAiBCAQIgQgECIEIBAiBCAQIgQgECIEIBAiBCAQIgQgECIEIBAiBCAQIgQgECIEIBAiBCAQIgQgECIEIBAiBCAQIgQgECIEIBAiBCAQIgQgECIEIBAiBCAQIgQgECIEIBAiBCAQIgQgECIEIBAiBCAQIgQgECIEIBAiBCAQEAAQEBIEABAQADAQEgPgEBAAUBPgAD","widgets":{"c":{"type":"canvas","size":[64,64],"pos":[2,2],"scale":8},"p":{"type":"slider","size":[64,17],"pos":[2,67],"interval":[2,27],"value":2,"format":"%i","style":"compact"}}}}}

Tired of the same old 16-color Macintosh palette? Taste the rainbow with

The PalImport Contraption:

Pick any 16-color (or fewer) palette from LoSpec, save the "HEX" format, and use the "Import" button on this contraption to read in the file or paste it directly into the field. Then click "Apply" to replace Decker's default palette. If the input palette has 14 or fewer colors, they will be sequentially loaded into color slots 33-46 and leave pure white (32) and pure black (47) intact. Otherwise, 32 and 47 will be prioritized with the lightest and darkest color available in the palette, respectively. Just like PatEdit, when you're satisfied with the results you can delete this contraption.

%%WGT0{"w":[{"name":"palImport1","type":"contraption","size":[137,100],"pos":[334,171],"def":"palImport","widgets":{"button1":{},"button2":{},"p":{"value":"2e222f\n45293f\n7a3045\n993d41\ncd683d\nfbb954\nf2ec8b\nb0a987\n997f73\n665964\n443846\n576069\n788a87\na9b2a2\n"}}}],"d":{"palImport":{"name":"palImport","size":[137,100],"margin":[0,0,0,0],"description":"a tool for importing color palettes in the .hex format as used by lospec.com.","image":"%%IMG2AIkAZAACAYUAAwEBDYUBAQABAQENhwECDYcBAg2HAQINhwECDYcBAg2HAQINhwECDYcBAg2HAQINhwECDYcBAg2HAQINhwECDYcBAg2HAQINhwECDYcBAg2HAQINhwECDYcBAg2HAQINhwECDYcBAg2HAQINhwECDYcBAg2HAQINhwECDYcBAg2HAQINhwECDYcBAg2HAQINhwECDYcBAg2HAQINhwECDYcBAg2HAQINhwECDYcBAg2HAQINhwECDYcBAg2HAQINhwECDYcBAg2HAQINhwECDYcBAg2HAQINhwECDYcBAg2HAQINhwECDYcBAg2HAQINhwECDYcBAg2HAQINhwECDYcBAg2HAQINhwECDYcBAg2HAQINhwECDYcBAg2HAQINhwECDYcBAg2HAQINhwECDYcBAg2HAQINhwECDYcBAg2HAQINhwECDYcBAg2HAQINhwECDYcBAg2HAQINhwECDYcBAg2HAQINhwECDYcBAg2HAQINhwECDYcBAg2HAQINhwECDYcBAg2HAQEAAQEBDYUBAQADAYUAAg==","widgets":{"button1":{"type":"button","size":[60,20],"pos":[6,74],"script":"on click do\n p.text:read[]\nend","text":"Import"},"button2":{"type":"button","size":[60,20],"pos":[71,74],"script":"on color_dist a b do\n aa:\"%2h%2h%2h\" parse \"%06h\" format a\n bb:\"%2h%2h%2h\" parse \"%06h\" format b\n sum(aa-bb)^2\nend\n\non find_closest i n do\n g:patterns[i]\n r:first n\n each v in n\n  if color_dist[r g]>color_dist[v g] r:v end\n end\n patterns[i]:r\n (list r) drop n\nend\n\non click do\n n:16 limit (list \"%h\") parse \"\\n\" split p.text\n patterns[32]:16777215\n patterns[47]:0\n if (count n)>15\n  n:find_closest[32 n]\n  n:find_closest[47 n]\n end\n each c i in n patterns[33+i]:c end\nend","text":"Apply"},"p":{"type":"field","size":[125,62],"pos":[6,7],"scrollbar":1,"style":"code"}}}}}
(1 edit)

Nice! This helps a lot as you can do like duotones, tritones, sepiatone sets and such.
This is a deck global color set, correct? A card, or per canvas widget property would even more helpful.

Is there a way to hide my widget/contraption invisible as a group? That way the can appear on a card only when called for? 

Also this Decker objects format, that starts some Decker clipboard data such as "%%WDG ..." or whatever, it looks like base64 encoded binary data like a Data URL?  I want to be able to decode these into human viewable formats and then reencoded them when done.

"%%IMG2AIkAZAACAYUAAwEBDYUBAQABAQENhwE ...

Thanks!

Developer

There is one pattern set/palette per deck, but as demonstrated by the above materials it can be manipulated on the fly; it's a matter of scripting to give cards their own palette. Some of the submissions to the most recent Decker game jam, including (Don't) Save Me and I Lost My Duck take advantage of this.

There is no grouping mechanism for widgets upon a card other than Contraptions.

Decker's image encodings are described in The Decker File Format. It's also possible to use Lilt to programmatically manipulate decks, encode/decode various resource types and import/export a handful of conventional file types.

Developer(+2)

Another palette-related editor,

The AnimEdit Contraption:


This contraption provides an editor for the four animated pattern "slots". Clicking "Read" will load the sequences of patterns in each slot into the text field, and clicking "Write" will apply the contents of the text field to the deck. The slider and canvas above provide a quick reference for all the currently defined colors and patterns, by index.

Each animated pattern (slots 28, 29, 30, and 31) consists of up to 8 indices into other patterns, shown comma-separated, one pattern per line.

%%WGT0{"w":[{"name":"animEdit1","type":"contraption","size":[155,99],"pos":[248,51],"def":"animEdit","widgets":{"v":{},"r":{},"w":{},"p":{},"c":{}}}],"d":{"animEdit":{"name":"animEdit","size":[155,99],"margin":[0,0,0,0],"description":"an editor for the animated pattern sequences.","image":"%%IMG2AJsAYwADAZYABAEBDZYBAQACAQENmAECDZkBAg2ZAQINmQECDZkBAg2ZAQINmQECDZkBAg2ZAQINmQECDZkBAg2ZAQINmQECDZkBAg2ZAQINmQECDZkBAg2ZAQINmQECDZkBAg2ZAQINmQECDZkBAg2ZAQINmQECDZkBAg2ZAQINmQECDZkBAg2ZAQINmQECDZkBAg2ZAQINmQECDZkBAg2ZAQINmQECDZkBAg2ZAQINmQECDZkBAg2ZAQINmQECDZkBAg2ZAQINmQECDZkBAg2ZAQINmQECDZkBAg2ZAQINmQECDZkBAg2ZAQINmQECDZkBAg2ZAQINmQECDZkBAg2ZAQINmQECDZkBAg2ZAQINmQECDZkBAg2ZAQINmQECDZkBAg2ZAQINmQECDZkBAg2ZAQINmQECDZkBAg2ZAQINmQECDZkBAg2ZAQINmQECDZkBAg2ZAQINmQECDZkBAg2ZAQINmQECDZkBAg2ZAQINmQECDZkBAg2ZAQINmQECDZkBAg2ZAQINmQEBAAEBAQ2XAQEAAwEBDZUBAQAFAZUAAw==","widgets":{"v":{"type":"field","size":[149,49],"pos":[3,25],"style":"code"},"r":{"type":"button","size":[72,20],"pos":[3,76],"script":"on click do\n v.text:\"\\n\" fuse each v in 28,29,30,31\n  \",\" fuse patterns[v]\n end\nend","text":"Read"},"w":{"type":"button","size":[74,20],"pos":[78,76],"script":"on click do\n each l i in 4 limit \"\\n\" split v.text\n  patterns[28+i]:0+\",\" split l\n end\nend","text":"Write"},"p":{"type":"slider","size":[72,20],"pos":[3,3],"script":"on change val do\n c.clear[]\n c.pattern:val\n c.fill[]\nend","interval":[0,47],"style":"compact"},"c":{"type":"canvas","size":[74,20],"pos":[78,3],"scale":1}}}}}
Developer (3 edits) (+4)

You spin me right round baby, like

The Rotor Contraption:


A rotor continuously rotates an image at a configurable speed. Rotors support transparency and can optionally be made draggable, like a canvas.

When pasting your image in, be sure to leave a sufficient margin: rotors spin around the centerpoint of the image, and only the circular part at the center of a rectangular image will spin "cleanly":

%%WGT0{"w":[{"name":"rotor1","type":"contraption","size":[100,100],"pos":[152,53],"show":"transparent","def":"rotor","widgets":{"c":{},"s":{},"i":{}}}],"d":{"rotor":{"name":"rotor","size":[100,100],"resizable":1,"margin":[5,5,5,5],"description":"Animate an image by making it spin!","script":"on get_speed   do 0+s.text  end\non set_speed x do s.text:x  end\non get_img   do i.value   end\non set_img x do i.value:x end\non get_draggable   do c.draggable   end\non set_draggable x do c.draggable:x end\n\non view do\n img:first extract arg where arg..type=\"image\" from get_img[]\n rot:(sys.frame/60)*2*pi*get_speed[]\n c.clear[]\n c.paste[img.copy[].rotate[rot] (c.size/2)-(img.size/2) 1]\nend","attributes":{"name":["speed","img","draggable"],"label":["Rotation Speed","Image","Draggable"],"type":["number","rich","bool"]},"widgets":{"c":{"type":"canvas","size":[100,100],"pos":[0,0],"locked":1,"animated":1,"script":"on drag do\n if me.draggable\n   card.pos:pointer.pos-me.size/2 \n   me.pos:0\n end\nend","show":"transparent","border":0,"scale":1},"s":{"type":"field","size":[23,20],"pos":[117,2],"show":"none","style":"plain","value":"0.5"},"i":{"type":"field","size":[23,20],"pos":[117,33],"show":"none","value":{"text":["","i"],"font":["",""],"arg":["","%%IMG2AEUAQwD/AP8A/wD/AHUBAQBDAQMAQgEEAEEBBABBAQUAPwEGAD8BBgA+AQcAPgEDDQEBAwA+AQMNAQEDAD4BAw0CAQMAPQEDDQIBAwA8AQMNAwEDADwBAw0DAQMAPAEDDQQBAwA6AQQNBAEDADoBAw0GAQMAOQEDDQYBAwA4AQMNBwEDADgBAw0IAQMANwEDDQkBAwA2AQMNCgEDADQBAw0MAQMAMgEDDQ0BAwAyAQMNDQEEADEBAw0OAQMAMQEDDQ4BAwAwAQMNDwEDADABAw0QAQMALwEWADABFQAvARUAMQEDAP8A/wD/AP8A/wAI"]}}}}}}

The rotor is also available in a variant,

The SeekRotor Contraption:

A seekRotor rotates to point toward something. By default, that "something" is the user's pointer. If you provide a "target[]" event handler on the contraption instance you can tell it to point toward any position you like. For example, pointing at the center of a field:


This behavior pairs nicely with making the source or target draggable, as seen above! Images for the seekRotor should face to the right in their initial view.

%%WGT0{"w":[{"name":"seekRotor1","type":"contraption","size":[100,100],"pos":[351,79],"show":"transparent","def":"seekRotor","widgets":{"c":{},"i":{}}}],"d":{"seekRotor":{"name":"seekRotor","size":[100,100],"resizable":1,"margin":[5,5,5,5],"description":"Animate an image by making turn toward the cursor.","script":"on get_img   do i.value   end\non set_img x do i.value:x end\non get_draggable   do c.draggable   end\non set_draggable x do c.draggable:x end\n\non view do\n img:first extract arg where arg..type=\"image\" from get_img[]\n trg:pointer.pos unless card.event[\"target\"]\n rot:heading trg-card.offset+card.size/2\n c.clear[]\n c.paste[img.copy[].rotate[rot] (c.size/2)-(img.size/2) 1]\nend","template":"on target do\n pointer.pos\nend","attributes":{"name":["img","draggable"],"label":["Image","Draggable"],"type":["rich","bool"]},"widgets":{"c":{"type":"canvas","size":[100,100],"pos":[0,0],"locked":1,"animated":1,"script":"on drag do\n if me.draggable\n   card.pos:pointer.pos-me.size/2 \n   me.pos:0\n end\nend\n","show":"transparent","border":0,"scale":1},"i":{"type":"field","size":[23,20],"pos":[117,33],"show":"none","value":{"text":["","i"],"font":["",""],"arg":["","%%IMG2AEUAQgD/AP8A/wD/AP8AfQEBAEMBBABBAQkAPAELADoBAwkBAQwANQEDCQYBCwAxAQMJCAEMAC4BAwkNAQkALAEDCREBCAApAQMJFAEJACUBAwkWAQgAJAEDCRkBBQAkAQMJFgEHACUBAwkTAQkAJgEDCQ8BCgApAQMJBgEQACwBAwkEAQ8ALwEDCQIBDQAzAQoAPAEGAD4BBgBAAQMA/wD/AP8A/wD/AP8ArA=="]}}}}}} EDCRkBBQAkAQMJFgEHACUBAwkTAQkAJgEDCQ8BCgApAQMJBgEQACwBAwkEAQ8ALwEDCQIBDQAzAQoAPAEGAD4BBgBAAQMA/wD/AP8A/wD/AP8ArA=="]}}}}}}
Developer(+2)

bored of base 10? how about

The BitField Contraption:

The bitField contraption (shown here linked to an ordinary field widget), stores a numeric value and allows it to be viewed and edited as binary digits. Clicking toggles a bit, and dragging can set multiple bits quickly. The "width" (in bits) can be configured to any value between 1 and 32. Like a Field widget, it fires an "on change" event, and it respects the "show", "font", and "locked" common attributes.

%%WGT0{"w":[{"name":"bitField","type":"contraption","size":[216,31],"pos":[176,159],"def":"bitField","widgets":{"c":{"size":[216,31],"pattern":0},"v":{"size":[65,31],"pos":[234,0],"value":"0"},"w":{"pos":[234,45]},"d":{"pos":[234,79]}}}],"d":{"bitField":{"name":"bitField","size":[101,20],"resizable":1,"margin":[5,5,5,5],"description":"a visual, interactive display of an integer as its bits, shown from most to least significant.","script":"on get_value   do 0+v.text end\non get_width   do 0+w.text end\non set_value x do v.text:((2^w.text)-1)&0|x view[] end\non set_width x do w.text:32&1|x             view[] end\n\nbw:get_width[]\nbv:get_value[]\n\non unpack do\n t:bv each v in range bw\n  r:2%t t:floor t/2 r\n end\nend\n\non view do\n c.clear[]\n c.font:card.font\n c.show:card.show\n cell:card.size/bw,1\n t:bv\n each bit i in unpack[]\n  r:cell*((bw-1)-i),0\n  c.pattern:1\n  if bit c.rect[r cell+1] end\n  c.pattern:!bit\n  c.text[bit r+cell/2 \"center\"]\n end\nend\n\non drag pos do\n bit:(bw-1)-floor pos[0]/card.size[0]/bw\n if !card.locked\n  set_value[if d.value\n   bits.and[bv bits.xor[(2^bw)-1 2^bit]]\n  else\n   bits.or[bv 2^bit]\n  end]\n  card.event[\"change\" get_value[]]\n end\nend\n\non click pos do\n bit:(bw-1)-floor pos[0]/card.size[0]/bw\n d.value:bits.and[bv 2^bit]\n drag[pos]\nend\n\non release pos do\n drag[pos]\nend","template":"on change val do\n \nend","attributes":{"name":["value","width"],"label":["Value","Width"],"type":["number","number"]},"widgets":{"c":{"type":"canvas","size":[101,20],"pos":[0,0],"locked":1,"border":1,"scale":1},"v":{"type":"field","size":[65,20],"pos":[119,0],"show":"none","style":"plain","value":"0"},"w":{"type":"field","size":[65,20],"pos":[119,34],"show":"none","style":"plain","value":"8"},"d":{"type":"button","size":[60,20],"pos":[119,68],"show":"none","style":"check","value":0}}}}}
Developer (1 edit) (+5)

Get the inside story with

The Interior Contraption:


The Interior contraption is a draggable object which reveals part of the background image from another card based on a "mask". Any part of the mask image that contains cyan pixels will be replaced as the contraption is moved around. If you specify the name of more than one card (in a comma-separated list), they will be cycled as an animation. A quick demo of setting this contraption up:


%%WGT0{"w":[{"name":"interior1","type":"contraption","size":[94,97],"pos":[282,140],"show":"transparent","def":"interior","widgets":{"c":{"size":[94,97]},"i":{"size":[100,28],"pos":[149,-5]},"n":{"size":[100,31],"pos":[149,46]}}}],"d":{"interior":{"name":"interior","size":[63,63],"resizable":1,"margin":[5,5,5,5],"description":"display an \"interior\" within the cyan pixels of a mask image composited in from another card.","script":"on get_mask do i.value end\non get_card do n.text  end\non set_mask x do i.value:x end\non set_card x do n.text:x  end\n\non view do\n mask:first extract arg where arg..type=\"image\" from i.value\n card.size:c.size:mask.size\n cn:\",\" split n.text\n cb:deck.cards[cn[(count cn)%floor sys.frame/10]].image\n if cb.type~\"image\"\n  cb:cb.copy[card.pos card.size].map[0 dict 32]\n  c.clear[]\n  c.paste[mask.copy[].map[(colors.cyan dict 1) 0]]\n  c.merge[mask.copy[] cb]\n end\nend\n\non drag do\n card.pos:pointer.pos-c.size/2\nend","attributes":{"name":["mask","card"],"label":["Mask","Card(s)"],"type":["rich","string"]},"widgets":{"c":{"type":"canvas","size":[63,63],"pos":[0,0],"locked":1,"animated":1,"show":"transparent","border":0,"draggable":1,"scale":1},"i":{"type":"field","size":[100,20],"pos":[118,-5],"locked":1,"show":"none","value":{"text":["","i"],"font":["",""],"arg":["","%%IMG2AD8APwFAJz0BAic9AQInPQECJz0BAic9AQInPQECJz0BAic9AQInPQECJz0BAic9AQInPQECJz0BAic9AQInPQECJz0BAic9AQInPQECJz0BAic9AQInPQECJz0BAic9AQInPQECJz0BAic9AQInPQECJz0BAic9AQInPQECJz0BAic9AQInPQECJz0BAic9AQInPQECJz0BAic9AQInPQECJz0BAic9AQInPQECJz0BAic9AQInPQECJz0BAic9AQInPQECJz0BAic9AQInPQECJz0BAic9AQInPQECJz0BAic9AQInPQECJz0BAic9AQInPQECJz0BQA=="]}},"n":{"type":"field","size":[100,20],"pos":[118,30],"locked":1,"show":"none","style":"plain"}}}}}
(+1)

man this is SO cool !!!!!!!

Developer(+2)

There are a lot of interesting ways this contraption- or similar compositing techniques- could be used. I'd love to see what you can come up with!

(2 edits) (+2)

I took on the (admittedly simple) challenge of emitting an event when the .beat internet time changes. I only discovered Decker today, so suggestions are welcome if I've misunderstood concepts or am using things wrong (i'm pretty sure preserving state between `view` events is not supposed to be done via an off-canvas textbox, but i wanted to avoid emitting an event more than once for the same timestamp), but here's an event-emitting .beat time box:


I've updated it to include a template for the beat_tick event, and  optimised things a little  by not doing any string formatting at all if the time hasn't changed  (reducing how much stuff happens at 60Hz), eliminating the parse call for sys.now (thanks for answering my modulo question over on cohost!)  and inlining the calculation in the format call. This results in a 0.06% reduction on the profiler.

%%WGT0{"w":[{"name":".beat1","type":"contraption","size":[88,28],"pos":[5,20],"def":".beat","widgets":{"t":{"value":"@632.49"},"utc_ts":{"value":"1695305447"},"beat_ts":{"value":"632.49"}}}],"d":{".beat":{"name":".beat","size":[88,28],"margin":[0,0,0,0],"description":"display the .beat time, also known as Swatch Internet Time.","script":"on get_ts do\n beat_ts.text\nend","template":"on beat_tick time do\n\nend","image":"%%IMG2AFgAHAACAVQAAwECIFIBAgABAQIgAQFSIAEBAyABAVQgAQECIAEBVCABAQIgAQFUIAEBAiABAVQgAQECIAEBBSACAU0gAQECIAEBBSACAU0gAQECIAEBBSACAU0gAQECIAEBBSACARQgAgE3IAEBAiABAQUgAgEUIAIBNyABAQIgAQEFIAUBAyAEAQMgAwEBIAEBASAEATYgAQECIAEBBSAGAQEgBgEBIAYBASAEATYgAQECIAEBBSACAQIgAgEBIAIBAiACAQEgAgECIAIBAiACATcgAQECIAEBBSACAQIgAgEBIAIBAiACAQEgAgECIAIBAiACATcgAQECIAEBBSACAQIgAgEBIAYBASACAQIgAgECIAIBNyABAQIgAQEFIAIBAiACAQEgAgEFIAIBAiACAQIgAgE3IAEBAiABAQUgBgEBIAYBASAGAQIgAgE3IAEBAiABAQMgAQEBIAUBAyAEAQMgAwEBIAEBAyACATYgAQECIAEBVCABAQIgAQFUIAEBAiABAVQgAQECIAEBVCABAQIgAQFUIAEBAyABAVIgAQECAAEBAiBSAQIAAwFUAAI=","attributes":{"name":[],"label":[],"type":[]},"widgets":{"t":{"type":"field","size":[54,15],"pos":[32,8],"locked":1,"animated":1,"script":"on view do\n if !(utc_ts.text~sys.now)\n  utc_ts.text:sys.now\n  d:\"%04.02f\" format ((1/86.4)*(86400%(sys.now+3600)))\n  card.event[\"beat_tick\" d]\n  beat_ts.text:d\n  me.text:\"@%s\" format beat_ts.text\n end\nend","font":"mono","show":"invert","border":0,"style":"plain","align":"right","value":"@632.44"},"utc_ts":{"type":"field","size":[47,14],"pos":[109,14],"show":"none","border":0,"value":"1695305443"},"beat_ts":{"type":"field","size":[47,13],"pos":[109,1],"show":"none","border":0,"value":"632.44"}}}}}
Developer(+1)

Nicely done!

It may seem a bit odd, but using hidden widgets is in fact the normal way to preserve state across events. Storing state in widgets means that it can always be viewed and inspected, and saving/reloading decks has straightforward, unsurprising semantics. It may be a good idea to mark such "internal" widgets in a contraption (or a deck itself) as "Show None".

Another refinement you could make is supplying a Template Script (under Prototype -> Properties...) with an empty "on beat_tick ... end" event handler; this helps make contraptions more self-documenting by indicating the events that are available and saving users typing, just like the templates Decker supplies automatically for the primitive widgets.

(+1)

Thanks for the hints - I’ve added the template script and such, and updated my post. I found that the time calculation itself could be optimised, since the call to parse "%e" sys.now and the math that followed was taking a timestamp, turning it into an object, then turning it back into a timestamp again (but where the start of the epoch is midnight of the current day, BMT), and it could be shortened to just (1/86.4) * (86400 % (sys.now + 3600)). Matters very, very little when the speed of the Lil interpreter is limited within Decker, but it feels better to me.

Developer (2 edits) (+1)

Trying to make a jump-scare, or at least a nice cupholder? Look no further than

The PopOut Contraption:

The PopOut Contraption behaves like a two-position switch, animating a configurable image in a smooth tween from one position to another when the image is clicked (or if anything else modifies its ".value" attribute). The contraption will automatically choose a vertical or horizontal orientation based on the bounding box of the contraption and the size of the source image, favoring the axis which allows the image more freedom of movement.


%%WGT0{"w":[{"name":"popOut1","type":"contraption","size":[45,84],"pos":[428,40],"script":"on click do\n popOut2.value:!popOut2.value\nend","show":"transparent","def":"popOut","widgets":{"c":{"size":[12,15],"pos":[16,0],"show":"transparent","image":"%%IMG2AAwADwABAbM="},"v":{"size":[60,11],"pos":[58,1],"value":1},"t":{"size":[64,11],"pos":[57,18],"value":"1"},"i":{"size":[66,11],"pos":[57,35]},"s":{"size":[100,11],"pos":[56,51],"value":"1.5"}}}],"d":{"popOut":{"name":"popOut","size":[101,148],"resizable":1,"margin":[0,0,0,0],"description":"A sliding animated element that alternates between two positions when clicked.","script":"on get_value do v.value end\non set_value x do v.value:x view[] end\non get_img do i.value end\non set_img x do i.value:x view[] end\non get_speed do 0+s.text end\non set_speed x do s.text:0|x end\n\non click do\n set_value[!get_value[]]\n card.event[\"click\"]\nend\non tween a b t do\n # see easeInOutBack() from easings.net\n c1:1.70158 c2:1.525\n a+(b-a)*.5*if !t>.5\n    ((2*t    )^2)*((c2+1)*(   2*t))-c2\n else\n  2+(((2*t)-2)^2)*((c2+1)*(-2+2*t))+c2\n end\nend\non view do\n img:first extract arg where arg..type=\"image\" from i.value\n c.size:img.size\n c.show:card.show\n c.paste[img]\n tn:0+t.text\n chg:if tn>v.value -1 elseif v.value>tn 1 else 0 end\n t.text:0|1&tn+chg*0.03*s.text\n c.animated:!tn~v.value\n d:card.size-c.size\n a:d[1]>d[0]\n tw:tween[2 d[a]-2 tn]\n ct:d[!a]/2\n c.pos:if a ct,tw else tw,ct end\nend","template":"on click do\n \nend","attributes":{"name":["img","speed","value"],"label":["Image","Speed","Value"],"type":["rich","number","bool"]},"widgets":{"c":{"type":"canvas","size":[26,26],"pos":[37,0],"locked":1,"border":0},"v":{"type":"button","size":[60,20],"pos":[114,2],"show":"none","style":"check","value":0},"t":{"type":"field","size":[64,20],"pos":[113,31],"show":"none","style":"plain","value":"0"},"i":{"type":"field","size":[66,20],"pos":[113,61],"show":"none","value":{"text":["","i"],"font":["",""],"arg":["","%%IMG2ABoAGgABARgAAQH/Af8BcgABARgAAQ=="]}},"s":{"type":"field","size":[100,20],"pos":[112,90],"show":"none","style":"plain","value":"1"}}}}}
Developer

PSA: several of the above contraption definitions were impacted by changes in Decker 1.32, and have been updated accordingly. If you're working on a deck that contains any of them (bouncer, eye, bob, pulse, rotor, seekRotor, interior, popOut), please refer to the release notes for help migrating.

Developer(+2)

by request,

The Chat Contraption:

The chat contraption provides a scrolling view of static text clustered into "speech bubbles" as in the text messaging UIs of most phones which can be advanced or retracted one bubble at a time.

The .value attribute contains rich text describing the chat log. Messages should be separated by a blank line (i.e. two newlines; \n\n). If a message is "!left" or "!right", it will switch which side of the conversation ensuing messages appear on instead of being displayed. Remember, you can use any other rich text formatting you like, including fonts and inline images!


The contraption exposes read-only .hasnext and .hasprev attributes indicating whether there are previous messages or next messages to show, respectively. It also exposes functions .next[] and .prev[] for stepping through message history. Any time you change the contraption's .value attribute the history will be reset to show only the first message.

The contraption respects the standard .show and .font attributes to control the transparency mode and default font of the chat log.

This contraption is provided below along with an enclosing card and prev/next buttons for paging through messages, to demonstrate how to use .hasprev/.hasnext/.prev[]/.next[]; these buttons are not part of the contraption itself and therefore can be freely customized or replaced to suit the needs of a particular application:

%%CRD0{"c":{"name":"card1","script":"on view do\n prev.locked:!chat1.hasprev\n next.locked:!chat1.hasnext\nend","widgets":{"chat1":{"type":"contraption","size":[196,155],"pos":[176,77],"font":"menu","show":"transparent","def":"chat","widgets":{"f":{"size":[196,155],"show":"transparent","value":{"text":["i"],"font":[""],"arg":["%%IMG2ALAAHAAFAWkARQFtAEIBbwBAAXEAPwFxAD4BcwA9AQUgAgFsAD0BBSACAWwAPQEFIAUBAyAEAQIgAgECIAIBWgA9AQUgAgECIAIBASACAQIgAgEBIAIBAiACAVoAPQEFIAIBAiACAQEgAgECIAIBASACAQIgAgFaAD0BBSACAQIgAgEBIAYBASACAQIgAgFaAD0BBSACAQIgAgEBIAIBBSACAQIgAgFaAD0BBSACAQIgAgEBIAIBAyABAQEgAgECIAIBWgA9AQUgAgECIAIBAiAEAQMgBQFaAD0BFyACAVoAPQETIAEBAyACAVoAPQEUIAQBWwA9AXIAPgFyAD4BcQA/AXAAQAFuAP8A/wD/ALU="]}},"msg":{"size":[100,31],"pos":[225,11],"value":{"text":["hey\n\ncan you read these?\n\n!right\n\nyeah\n\nwhat's up\n\n...\n\ncome on, man, don't keep me hanging here forever\n\n!left\n\nsorry\n\nlooks like the chat contraption works\n\n!right\n\n","sweet!","\n\n!left\n\nkeep in mind that there's kind of a soft limit to how many messages we can display\n\n!right\n\ngot it\n\nsince the messages are updated in response to an attribute change we have to stay in quota\n\n!left\n\nexactly\n\nbut as you can see the limit is reasonably high for practical use\n\n!right\n\nyeah this isn't too bad\n\nwe could also try to use longer sentences, since the cost is per speech bubble rather than per character\n\n!left\n\ntruth\n\nanyway, hopefully this contraption is handy!"],"font":["","menu",""],"arg":["","",""]}},"idx":{"size":[100,31],"pos":[225,53]},"tmp":{"pos":[225,91],"font":"menu"},"cnt":{"size":[100,31],"pos":[225,110],"value":"18"}}},"next":{"type":"button","size":[60,20],"pos":[280,260],"script":"on click do\n chat1.next[]\n view[]\nend","text":"Next"},"prev":{"type":"button","size":[60,20],"pos":[202,260],"locked":1,"script":"on click do\n chat1.prev[]\n view[]\nend","text":"Prev"}}},"d":{"chat":{"name":"chat","size":[100,100],"resizable":1,"margin":[5,5,5,5],"description":"a semi-interactive scrollable chat log.","script":"on get_value   do msg.value end\non set_value x do msg.value:x idx.text:0 view[] end\n\non view do\n f.show:card.show\n tmp.font:card.font\n tw:(first f.size)-20\n mw:floor .6*tw\n d :0\n r :rtext.cat[]\n margin:4 take 5\n lborder:image[\"%%IMG0AAwADAYAH4A/wH/gf+D/8P/w/+D/4P/A/4D+AA==\"]\n rborder:image[\"%%IMG2AAwADAAFAQIACAECIAIBAgAFAQEgBgEBAAMBASAIAQEAAgEBIAgBAQABAQEgCgECIAoBAQABAQEgCQEBAAEBASAJAQEAAgEBIAgBAQADAQIgBgEBAAUBBw==\"]\n   \n each chunk in rtext.split[\"\\n\\n\" msg.value]\n  str:rtext.string[chunk]\n  if     \"!left\" ~str d:0\n  elseif \"!right\"~str d:1\n  else\n   mh:last tmp.textsize[chunk mw]\n   sz:mw,mh\n   tmp.size:tw,mh+15\n   tmp.clear[]\n   if d # right-half\n    tmp.segment[rborder ((tw-mw+10),0),(10+sz) margin]\n    tmp.pattern:1\n    tmp.text[chunk ((tw-mw+5),5),sz \"top_right\"]\n   else # left-half\n    tmp.segment[lborder (0,0,10+sz) margin]\n    tmp.pattern:32\n    tmp.text[chunk (5,5,sz) \"top_left\"]\n   end\n   r:r,rtext.cat[tmp.copy[]]\n  end\n end\n tmp.size:1,1\n cnt.text:count r\n i:1+0|idx.text\n r:i limit r\n f.value:r\nend\n\non get_animate do view end\non get_hasprev do (0+idx.text)>0          end\non get_hasnext do (0+cnt.text)>1+idx.text end\non get_prev do\n on prev do\n  if get_hasprev[] idx.text:idx.text-1 view[] f.scroll:9999999 end\n end\nend\non get_next do\n on next do\n  if get_hasnext[] idx.text:idx.text+1 view[] f.scroll:9999999 end\n end\nend\n","attributes":{"name":["value"],"label":["Value"],"type":["rich"]},"widgets":{"f":{"type":"field","size":[100,100],"pos":[0,0],"locked":1,"scrollbar":1},"msg":{"type":"field","size":[100,20],"pos":[129,7],"locked":1,"show":"none"},"idx":{"type":"field","size":[100,20],"pos":[129,34],"locked":1,"show":"none","value":"0"},"tmp":{"type":"canvas","size":[1,1],"pos":[129,59],"locked":1,"image":"%%IMG0AAEAAQA=","pattern":32,"scale":1},"cnt":{"type":"field","size":[100,20],"pos":[129,71],"locked":1,"show":"none","value":"1"}}}}}

A question: if I've used a richtext link in the chatlog is there any way to have the link be clickable?

Developer

No; unfortunately the way this contraption works requires rendering out every message as an inline image, which bakes links down into only their visual appearance (the dotted underline).

Ahh, no worries. That is pretty neat though!

(5 edits) (+4)

itemlist contraption

This contraption lets you store a list of items. Basically it wraps a two-column grid, but the second column is hidden and replaced with a big text area.

The buttons at the top do the following:

  • -> "run", passes the current text to a configurable event handler on the card (run_event attribute contains the event name to trigger). Using shift-enter with the editor focused will also trigger this event, either on the entire item text or the selection. 
  • up/dn -> moves the current item up or down in the list of items
  • </> -> lets you navigate between the items in order. (you can click the names in the list to do this, but you can also hide the names by setting the list_width attribute to 0) 
  • - -> deletes the current item (shows a confirmation box first)
  • + -> adds a new item

There are also two helper functions you can use to get access to the underlying data:

  • .ls[] -> returns the entire items.value table
  • .current[] -> returns the current row from the table.

<couldn't get the widget to paste correctly. see comment below for link to a .deck>

It looks like the code might've been pasted wrong? I tried importing it into my deck and it didn't load correctly. I would love it if this was fixed since it does exactly what I need!

(+2)

I can't seem to get the widget by itself to paste correctly. I tried uploading it to another site but it just comes back blank.
Meanwhile, I just uploaded an entire deck with nothing but the widget:
https://gist.github.com/tangentstorm/37a26ebf0dc438eae1e765675b73ecd0

(+1)

tysm!! Using this to have a database card in my character sheet for the TTRPG I play with my friends :D

(1 edit) (+7)

dialogizer styler

A contraption to quickly generate style code snippets for Dialogizer. Get what you need then delete it when you're done.

%%WGT0{"w":[{"name":"dialogizer styler1","type":"contraption","size":[289,187],"pos":[49,48],"show":"transparent","def":"dialogizer styler","widgets":{"fontlist":{},"fct":{},"bct":{},"fclr":{"value":"47"},"bclr":{"value":"32"},"tft":{},"tfnt":{},"patpal":{},"traplist":{"row":18},"tbr":{},"brdr":{},"button1":{},"idi":{"value":"p"},"result":{},"sti":{}}}],"d":{"dialogizer styler":{"name":"dialogizer styler","size":[289,187],"margin":[0,0,0,0],"description":"generate style code","script":"on view do\nfontlist.value:select name:key from deck.fonts\nfprv.font:first fontlist.rowvalue\nfprv.text:first fontlist.rowvalue\ntfnt.font:first fontlist.rowvalue\ntfnt.text:first fontlist.rowvalue\ntraplist.value:select name:key from deck.contraptions\nbrdr.text:first traplist.rowvalue\nend","image":"%%IMG2ASEAuwD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wAUIQEA/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wDZ","widgets":{"fontlist":{"type":"grid","size":[97,176],"pos":[96,11],"locked":1,"script":"on click row do\n view[]\nend","headers":0,"value":{"name":["body","menu","mono"]},"row":0},"fct":{"type":"field","size":[48,16],"pos":[0,11],"locked":1,"font":"mono","border":0,"align":"right","value":"fcolor:"},"bct":{"type":"field","size":[48,16],"pos":[0,25],"locked":1,"font":"mono","border":0,"align":"right","value":"bcolor:"},"fclr":{"type":"field","size":[49,16],"pos":[48,11],"font":"mono","border":1,"value":"32"},"bclr":{"type":"field","size":[49,16],"pos":[48,26],"font":"mono","border":1,"value":"47"},"tft":{"type":"field","size":[48,16],"pos":[0,40],"locked":1,"font":"mono","border":0,"align":"right","value":"tfont:"},"tfnt":{"type":"field","size":[49,16],"pos":[48,41],"border":1,"value":"body"},"patpal":{"type":"canvas","size":[288,10],"pos":[0,1],"locked":1,"border":0,"image":"%%IMG2ASAACiASARMhESISIxIkEiUSJhInEigSKRIqEisSLBItEi4SIBIBEyERIhIjEiQSJRImEicSKBIpEioSKxIsEi0SLhIgBAEEIAIBBCAEAQQgAQEFIAUBBCEDAQQhAgEEIQQiBAEEIgIBASIHIwQBBCMDAQMjBCQEAQQkAgEBJAclBAEEJQIBBSUDJgQBBCYDAQImBScEAQQnAgEEJwQoBAEBKAUBBCgEKQQBASkFAQMpBSoEAQEqBQEEKgQrBAEBKwUBBCsELAQBASwFAQEsBy0EAQEtBgEDLQQuBAEBLgUBAS4HIAcBASAFAQEgBAEEIAEBASABAQcgAQEEIQYBASEFAQEhBCIHAQEiAgEBIgEBASIFIwcBASMDAQEjBiQHAQEkAgEBJAclBwEBJQYBASUDJgcBASYDAQImBScHAQEnAgEBJwIBAScEKAQBASgBAQEoAwEBKAIBASgEKQQBASkBAQEpBQEBKQUqBAEBKgEBASoGAQEqBCsEAQErAQEBKwYBASsELAQBASwBAQEsAwEBLAEBASwFLQQBAS0BAQEtBAEBLQYuBAEBLgEBAS4DAQEuByAHAQEgBQEBIAQBBCABAQEgAQEHIAEBBCEGAQEhBQEBIQQiBwEBIgIBASIBAQEiBSMHAQEjAwEBIwYkBwEBJAIBASQHJQcBASUGAQElAyYHAQEmAwECJgUnBwEBJwIBBCcEKAQBASgBAQEoAwEBKAIBASgEKQQBASkBAQEpBQEBKQUqBAEBKgEBASoGAQEqBCsEAQErAQEBKwYBASsELAQBASwBAQEsAwEBLAEBASwFLQQBAS0BAQEtBAEBLQYuBAEBLgEBAS4DAQEuByAFAQIgAwEEIAQBBCADAQUgAgEFIQQBAiEEAQIhBSIFAQIiAwEDIgUjBQECIwQBAyMEJAUBAiQDAQQkBCUFAQIlBQECJQQmBQECJgMBBCYEJwUBAicGAQEnBCgEAQMoAwEBKAIBASgEKQQBAykFAQEpAQEBKQMqBAEDKgMBBCoEKwQBAysEAQIrBSwEAQMsAwEDLAUtBAEDLQQBAy0ELgQBAy4DAQQuBCAHAQEgAgEBIAcBBiABAQUgAQEGIQYBASEFAQEhBCIHAQEiBAEBIgUjBwEBIwUBASMEJAcBASQCAQEkAgEBJAQlBwEBJQQBASUFJgcBASYCAQEmAgEBJgQnBwEBJwUBAScEKAYBASgDAQEoAgEBKAQpBgEBKQUBASkBAQEpAyoGAQEqAwEBKgcrBgEBKwYBASsELAYBASwFAQEsBS0GAQEtBgEBLQQuBgEBLgMBAS4CAQEuBCAEAQQgAgEEIAQBBiABAQUgAQEGIQMBBCECAQQhBCIEAQQiBAEBIgUjBAEEIwIBBCMEJAQBBCQCAQQkBCUEAQQlBAEBJQUmBAEEJgIBBCYEJwQBBCcFAQEnBCgGAQEoAwEEKAQpBgEBKQMBBSkDKgYBASoDAQQqBCsGAQErAwEEKwQsBgEBLAUBASwFLQYBAS0DAQQtBC4GAQEuAwEELgQgEgETIREiEiMSJBIlEiYSJxIoEikSKhIrEiwSLRIuEiASARMhESISIxIkEiUSJhInEigSKRIqEisSLBItEi4S","scale":1},"traplist":{"type":"grid","size":[96,176],"pos":[192,11],"locked":1,"script":"on click row do\n view[]\nend","headers":0,"value":{"name":["dialogizer styler"]},"row":2},"tbr":{"type":"field","size":[48,16],"pos":[0,55],"locked":1,"font":"mono","border":0,"align":"right","value":"border:"},"brdr":{"type":"field","size":[49,16],"pos":[48,56],"border":1,"value":"dialogizer styler"},"button1":{"type":"button","size":[96,16],"pos":[0,89],"script":"on click do\nresult.text:\"%s:()\n%s.fcolor:%s\n%s.bcolor:%s\n%s.tfont:%s\n%s.border:deck.contraptions.%s\ndd.style[%s]\" format idi.text,idi.text,fclr.text,idi.text,bclr.text,idi.text,tfnt.text,idi.text,brdr.text,idi.text\nend","text":"generate","style":"rect"},"idi":{"type":"field","size":[49,16],"pos":[48,71],"font":"mono","border":1,"value":"o"},"result":{"type":"field","size":[97,80],"pos":[0,107],"script":"on change val do\n \nend"},"sti":{"type":"field","size":[48,16],"pos":[0,70],"locked":1,"font":"mono","border":0,"align":"right","value":"ID:"}}}}}
Developer(+2)

Feel like your life just isn't sliding into place? Experience catharsis with

ThatPuzzle Contraption

The ThatPuzzle contraption is a classic sliding tile puzzle often called a "15-puzzle", in contraption form. Configure its dimensions from 2x2 to 10x10 tiles, and it will automatically display an image sliced up from the card background it's placed over. There are even event hooks and attributes for recognizing a successful solution!

  • thatpuzzle.dim : r/w integer from 2 to 10; the number of tiles per axis.
  • thatpuzzle.solved: read-only bool; is the puzzle currently solved?
  • thatpuzzle.reset[] : reset the tiles to a solved configuration.
  • thatpuzzle.scramble[] : randomize the tiles. for large puzzles you may need to repeat this a few times for a thorough mixing.
  • on change do .. end : fired whenever a user moves a tile.
%%WGT0{"w":[{"name":"thatpuzzle1","type":"contraption","size":[100,100],"pos":[199,126],"script":"on change do\n won.value:me.solved\nend","def":"thatpuzzle","widgets":{"c":{},"s":{"value":"[]"},"dim":{"value":"3"}}}],"d":{"thatpuzzle":{"name":"thatpuzzle","size":[100,100],"resizable":1,"margin":[5,5,5,5],"description":"the classic sliding-tile puzzle. you know, that one.","script":"BG:image[\"%%IMG2AAYABgEIDQMBAiABAQENAgECIAIBBCACAQEMAQEH\"]\nFG:image[\"%%IMG2AAkACQEGAAMBASAEAQIAAgEBIAQBASABAQEAAQEBIAQBASACAQIgBAEBIAIBByACAQEAAQEBDQQBASABAQEAAgEBDQQBAgADAQY=\"]\nBG_M:4,4,1,1\nFG_M:4,4,4,4\nDELTAS:\"%j\" parse \"[[-1,0],[0,-1],[1,0],[0,1]]\"\nSTEPS:30\n\non get_dim do 0+dim.text end\non set_dim x do dim.text:10&2|x end\n\non swap a x y do t:a[x] a[x]:a[y] a[y]:t a end\non initstate do d:get_dim[] (d^2)%1+range d^2 end\non setstate x do s.text:\"%j\" format list x end\non getstate do\n d:get_dim[]\n r:\"%j\" parse s.text\n if (d^2)~count r r else initstate[] end\nend\n\non view do\n d:get_dim[]\n b:getstate[]\n cell:c.size/d\n c.segment[BG 0,0,c.size BG_M]\n o:d cross d\n tiles:select orderby p asc where v from select v:b p:o from ()\n each row in rows tiles\n  c.segment[FG (row.p*cell),(cell+2 drop FG_M) FG_M]\n  bg:deck.card.image.copy[card.pos+o[row.v-1]*cell (cell,cell)-1]\n  c.paste[bg (1+row.p*cell)]\n end\nend\n\non click pos do\n d:get_dim[]\n b:nb:getstate[]\n o:(d cross d) dict range d^2\n p:floor pos/c.size/d\n each delta in DELTAS\n  t:p+delta\n  if (0~b[o[t]])&(!max(t<0)|(t>d-1)) nb:swap[b o[t] o[p]] end\n end\n setstate[nb]\n view[]\n if !nb~b card.event[\"change\"] end\nend\n\non get_reset do\n on reset do\n  setstate[()]\n  view[]\n end\nend\n\non get_scramble do\n on scramble do\n  d:get_dim[]\n  b:getstate[]\n  o:(d cross d) dict range d^2\n  p:first extract key where 0=b from o\n  each in range 100\n   t:p+random[DELTAS]\n   if !max(t<0)|(t>d-1) b:swap[b o[t] o[p]] p:t end\n  end\n  setstate[b]\n  view[]\n end\nend\n\non get_solved do\n getstate[]~initstate[]\nend","template":"on change do\n \nend","attributes":{"name":["dim"],"label":["Dim"],"type":["number"]},"widgets":{"c":{"type":"canvas","size":[100,100],"pos":[0,0],"locked":1,"volatile":1,"scale":1},"s":{"type":"field","size":[100,20],"pos":[117,13],"show":"none"},"dim":{"type":"field","size":[100,20],"pos":[117,47],"show":"none","value":"4"}}}}}
Developer(+4)

Tired of having boring checkboxes? Tired of having a boring life? Spice things up with

The ToggleButton Contraption


The toggleButton contraption behaves as a drop-in replacement for a "checkbox"-styled button widget, using customizable graphics instead of a textual label. Just like a Button widget, this produces click[] events when clicked, exposes a boolean .value attribute, and respects .show for transparency. If provided with a single image, the image will be displayed only if the value is 1. Otherwise, the first image will be used to indicate a value of 0 and the last image will be used to indicate a value of 1. If images are smaller than the bounding box of the contraption, they will be drawn centered.

%%WGT0{"w":[{"name":"toggleSwitch","type":"contraption","size":[53,31],"pos":[155,141],"show":"transparent","def":"toggleButton","widgets":{"c":{"size":[53,31],"show":"transparent"},"b":{"size":[53,31]},"ims":{"size":[100,6],"pos":[74,2],"value":{"text":["","i","i"],"font":["","",""],"arg":["","%%IMG2ADMAHQAoAQIALwECIAIBAQAsAQIgBAECACkBAiAGAQIAJwECIAQNAiACAQEMAQEBAAgBHiAGDQIgAgEBDAEBBQADAQEgGQEDIAUNASACDQIgAgEBDAIBASADAQEAAQEBIBcBAyAHDQMgAQ0CIAIBAQwDAQEgAwECIAMBFCAHDQEgAg0GIAIBAQwDAQEgAwECIAIBFCAGDQQgAQ0GIAIBAQwEAQEgAgECIAIBAiAXDQUgAQ0GIAIBAQwEAQEgAgECIAIBASAYDQIgAQ0CIAENBiACAQEMBAEBIAIBAiACAQEgGA0CIAENAiABDQIgAQ0DIAIBAQwEAQEgAgECIAIBASADAQMgAgEEIAEBBCAHDQIgAQ0CIAENAiACDQIgAgEBDAQBASACAQIgAgEBIAIBBSABAQQgAQEEIAcNAiABDQIgAQ0CIAMNASACAQEMBAEBIAIBAiACAQEgAgECIAEBAiABAQIgAwECIAkNAiABDQIgAQ0CIAYBAQwEAQEgAgECIAIBASACAQIgAQECIAEBBCABAQQgBw0FIAkBAQwEAQEgAgECIAIBASACAQIgAQECIAEBBCABAQQgBw0FIAcBAwwEAQEgAgECIAIBASACAQIgAQECIAEBAiADAQIgCg0CIAcBAg0DAQEMAwEBIAIBAiACAQEgAgECIAEBAiABAQIgAwECIBEBAg0FAQEMAwEBIAIBAiACAQEgAgEFIAEBAiADAQIgDwECDQgBAQwCAQEgAgECIAIBASADAQMgAgECIAMBAiANAQINCgEBDAIBASACAQIgAgEBIBoBAg0NAQEMAQEBIAIBAiACAQEgFwEDDQ8BAQwBAQEgAgECIAIBASAUAQMNEwECIAIBAiADASsgAwECIDEBAQABAQEgLwEBAAMBLwAC","%%IMG2ADMAHQAJAQIAMAEBIAIBAgAtAQIgBAECACsBAiAGAQIAKAEBDAEBASAIAQIAIgEFDAEBASADDQIgBQEeAAMBASADAQEMAgEBIAINBCAGAQMgGQEBAAEBASADAQEMAwEBIAINAiABDQIgAQ0BIAYBAyAXAQIgAwEBDAMBASACDQIgAQ0CIAENAiAIARQgAwECIAIBAQwEAQEgAg0CIAENAiABDQMgAQ0BIAYBFCACAQIgAgEBDAQBASACDQIgAQ0CIAENAyABDQIgFwECIAIBAiACAQEMBAEBIAINAiABDQIgAQ0CIAINAyAXAQEgAgECIAIBAQwEAQEgAg0CIAENAiABDQMgAQ0DIBcBASACAQIgAgEBDAQBASACDQIgAQ0CIAENAyABDQIgCwEDIAIBAiACAQIgAgEBIAIBAiACAQEMBAEBIAINBSABDQMgAQ0DIAkBBSABAQMgAQECIAIBASACAQIgAgEBDAQBASAEDQIgAg0CIAINAyAJAQIgAQECIAEBAyABAQIgAgEBIAIBAiACAQEMBAEBIAgNAiACDQIgCgECIAEBAiABAQYgAgEBIAIBAiACAQEMBAEDIAcNASACDQIgCgECIAEBAiABAQYgAgEBIAIBAiACAQEMAwEBDQMBAiAIDQIgCgECIAEBAiABAQIgAQEDIAIBASACAQIgAgEBDAMBAQ0FAQIgBw0BIAoBAiABAQIgAQECIAEBAyACAQEgAgECIAIBAQwCAQENCAECIBABBSABAQIgAgECIAIBASACAQIgAgEBDAIBAQ0KAQIgDwEDIAIBAiACAQIgAgEBIAIBAiACAQEMAQEBDQ0BAiAaAQEgAgECIAIBAQwBAQENDwEDIBcBASACAQIgAgECDRMBAyAUAQEgAgECIAMBKyADAQIgMQEBAAEBASAvAQEAAwEvAAI="]}}}}],"d":{"toggleButton":{"name":"toggleButton","size":[100,100],"resizable":1,"margin":[1,1,1,1],"description":"A graphical drop-in replacement for checkboxes.","script":"on view do\n i:extract arg where arg..type=\"image\" from ims.value\n c.show:card.show\n c.clear[]\n if 1~count i\n  f:first i\n  if b.value c.paste[f .5*c.size-f.size] end \n end\n if 2~count i\n  f:i[b.value]\n  c.paste[f .5*c.size-f.size]\n end\nend\n\non click do\n set_value[!get_value[]]\n card.event[\"click\"]\nend\n\non get_value do b.value end\non set_value x do b.value:x view[] end\non get_images do ims.value end\non set_images x do ims.value:x end","template":"on click do\n \nend","attributes":{"name":["value","images"],"label":["Value","Images"],"type":["bool","rich"]},"widgets":{"c":{"type":"canvas","size":[100,100],"pos":[0,0],"locked":1,"volatile":1,"border":0,"scale":1},"b":{"type":"button","size":[100,100],"pos":[0,0],"script":"on click do\n \nend","show":"transparent","style":"invisible"},"ims":{"type":"field","size":[100,20],"pos":[121,5],"show":"none","border":1,"value":{"text":["","i"],"font":["",""],"arg":["","%%IMG2AA8ADwACAQsAAwEBIAsBAQABAQEgDQECIA0BAiANAQIgDQECIA0BAiANAQIgDQECIA0BAiANAQIgDQECIA0BAQABAQEgCwEBAAMBCwAC"]}}}}}}

Do you want the Juice? Do you need the Juice? Juice it or lose it with

The AnimButton Contraption

The animButton is another drop-in replacement for button widgets, this time for ordinary buttons. Just like a Button widget, this produces click[] events when clicked,  and respects .show for transparency. The first image is the default appearance of the button. When clicked, the button will cycle forward-and-back through the provided images. For example, if there are 3 images {0,1,2}, they will be displayed in the order {0,1,2,1,0}. If images are smaller than the bounding box of the contraption, they will be drawn centered.

%%WGT0{"w":[{"name":"animButton1","type":"contraption","size":[29,27],"pos":[61,81],"show":"transparent","def":"animButton","widgets":{"c":{"size":[29,27],"show":"transparent"},"b":{"size":[29,27]},"ims":{"size":[100,6],"pos":[50,1],"value":{"text":["","i","i","i","i"],"font":["","","","",""],"arg":["","%%IMG2ABkAGQABARcAAQEBIBcBAiAXAQIgFwECIBcBAiAXAQIgFwECIBcBAiAXAQIgFwECIBcBAiAXAQIgFwECIBcBAiAXAQIgFwECIBcBAiAXARsNFwECDRcBAg0XAQINFwECDRcBGg==","%%IMG2ABkAGQBMARcAAQEBDBcBAgwXAQIMFwECDBcBAgwXAQIMFwECDBcBAgwXAQIMFwECDBcBAgwXAQIMFwECDBcBAgwXAQIMFwECDBcBAgwXARsNFwECDRcBGg==","%%IMG2ABkAGQCWARoMFwECDBcBAgwXAQIMFwECDBcBAgwXAQIMFwECDBcBAgwXAQIMFwECDBcBAgwXAQIMFwECDBcBAgwXAQIMFwECDBcBGg==","%%IMG2ABkAGQCWAWYMFQEDDBcBAgwXAQIMFwECDBcBAgwXAQIMFwECDBcBAgwXAQIMFwECDBcBAgwXAQIMFwECDBcBGg=="]}},"ai":{"size":[100,5],"pos":[50,10],"value":"12"}}}],"d":{"animButton":{"name":"animButton","size":[100,100],"resizable":1,"margin":[1,1,1,1],"description":"A graphical drop-in replacement for buttons.","script":"on rev x do x @ (-1+count x)-range count x end\n\non view do\n i:extract arg where arg..type=\"image\" from ims.value\n f:first i\n c.show:card.show\n c.clear[]\n if c.animated\n  s:(range count i),1 drop rev[range count i]\n  a:floor ai.text/2\n  f:f unless i[s[a]]\n  if a~(count s)-1 c.animated:b.locked:0 else ai.text:1+ai.text end\n end\n c.paste[f .5*c.size-f.size]\nend\n\non click do\n if !b.locked\n  ai.text:0\n  c.animated:b.locked:1\n  view[]\n  card.event[\"click\"]\n end\nend\n\non get_images do ims.value end\non set_images x do ims.value:x end","template":"on click do\n \nend","attributes":{"name":["images"],"label":["Images"],"type":["rich"]},"widgets":{"c":{"type":"canvas","size":[100,100],"pos":[0,0],"locked":1,"volatile":1,"border":0,"scale":1},"b":{"type":"button","size":[100,100],"pos":[0,0],"show":"transparent","style":"invisible"},"ims":{"type":"field","size":[100,20],"pos":[121,5],"show":"none","border":1},"ai":{"type":"field","size":[100,20],"pos":[121,37],"show":"none","style":"plain"}}}}}
Developer (1 edit) (+3)

Do you pine for diet colas from the 1970s? Indulge yourself with

The TabBar Contraption:


Configure a set of newline-delimited options, and this contraption allows you to make an exclusive selection among them. In concert with showing/hiding other widgets or manipulating the data stored in other widgets, this can be used to create the appearance of "tabbed" UIs. This contraption supports the standard .show, .font, and .locked attributes. You can obtain the list of options with the .options attribute, and the selected tab (by name) with the .value attribute. When a tab is selected, this contraption provides a click[] event with no arguments.

%%WGT0{"w":[{"name":"tabbar","type":"contraption","size":[168,34],"pos":[184,140],"def":"tabbar","widgets":{"o":{"size":[100,7],"pos":[191,0]},"c":{"size":[168,34]},"i":{"size":[100,8],"pos":[191,11]}}}],"d":{"tabbar":{"name":"tabbar","size":[100,100],"resizable":1,"margin":[1,1,1,1],"description":"a tab bar, allowing the user to make an exclusive selection among several choices.","version":1.1,"script":"on get_options_text do o.text end\non set_options_text x do\n o.text:x\n i.interval:0,(count \"\\n\" split o.text)-1\n view[]\nend\non get_options do \"\\n\" split o.text end\non set_options x do set_options_text[\"\\n\" fuse x] x end\non get_value do get_options[][i.value] end\non set_value x do v:get_options[] i.value:sum (range count v)*v=x x view[] end\n\nbf:image[\"%%IMG2AAsABwADAQUABQEBIAUBAQADAQEgBwEBAAEBASAJAQIgCQECIAkBDA==\"] bb:image[\"%%IMG2AAsABwADAQUABQEBIAUBAQADAQEgAQEFIAEBAQABAQEgAQEHIAEBAiABAQcgAQECIAEBByABAQw=\"]\n\non view do\n c.show:card.show\n c.font:card.font\n c.clear[]\n v:get_options[]\n w:c.size[0]/count v\n each x ix in v\n  sel:ix~i.value\n  b:(ix*w),0,(w+ix<-1+count v),c.size[1]\n  c.segment[(bf,bb)[sel] b 5,4,5,2]\n  c.pattern:(1,32)[sel]\n  c.text[x b+(0,1,0,0) \"center\"]\n end\nend\n\non click pos do\n if !card.locked\n  v:get_options[]\n  w:c.size[0]/count v\n  p:get_value[]\n  set_value[v[floor(first pos)/w]]\n  if !p~get_value[] card.event.click end\n end\nend","template":"on click do\n \nend","attributes":{"name":["options_text"],"label":["Options"],"type":["code"]},"widgets":{"o":{"type":"field","size":[100,20],"pos":[123,1],"show":"none","style":"plain","value":"One\nTwo\nThree"},"c":{"type":"canvas","size":[100,100],"pos":[0,0],"locked":1,"volatile":1,"border":0,"scale":1},"i":{"type":"slider","size":[100,25],"pos":[123,32],"show":"none","interval":[0,2]}}}}}
Developer(+3)

Another variation that might be useful for prototyping and documentation-

The scriptViewer Contraption:

A tabbed text field which allows the user to view (or edit!) the script of several widgets on the current card. The "Sources" should be a newline-delimited list of widget names. If "card" or "deck" are specified, the scriptViewer will access the script of the current card or the deck, respectively.

%%WGT0{"w":[{"name":"scriptViewer","type":"contraption","size":[189,106],"pos":[170,144],"def":"scriptViewer","widgets":{"o":{"size":[100,21],"pos":[212,1],"value":"deck\ncard"},"c":{"size":[189,18]},"i":{"size":[100,26],"pos":[212,34],"interval":[0,1]},"scr":{"size":[185,85]}}}],"d":{"scriptViewer":{"name":"scriptViewer","size":[100,100],"resizable":1,"margin":[3,20,3,3],"description":"a tabbed editor for viewing and editing the scripts of one or more widgets on the current card.","version":1,"script":"on get_options_text do o.text end\non set_options_text x do\n o.text:x\n i.interval:0,(count \"\\n\" split o.text)-1\n view[]\nend\non get_options do \"\\n\" split o.text end\non set_options x do set_options_text[\"\\n\" fuse x] x end\non get_value do get_options[][i.value] end\non set_value x do v:get_options[] i.value:sum (range count v)*v=x x view[] end\n\nbf:image[\"%%IMG2AAsABwADAQUABQEBIAUBAQADAQEgBwEBAAEBASAJAQIgCQECIAkBDA==\"] bb:image[\"%%IMG2AAsABwADAQUABQEBIAUBAQADAQEgAQEFIAEBAQABAQEgAQEHIAEBAiABAQcgAQECIAEBByABAQIgAQEHIAEBAQ==\"]\n\non target do\n val:get_value[]\n if val~\"deck\" deck elseif val~\"card\" deck.card else deck.card.widgets[val] end\nend\non change do\n target[].script:scr.text\nend\n\non view do\n c.font:card.font\n c.clear[]\n v:get_options[]\n w:(.7*c.size[0])/count v\n each x ix in v\n  sel:ix~i.value\n  b:(ix*w),0,(w+ix<-1+count v),c.size[1]\n  c.segment[(bf,bb)[sel] b 5,4,5,2]\n  c.pattern:(1,32)[sel]\n  c.text[x b+(0,1,0,0) \"center\"]\n end\n scr.text:target[].script\n scr.locked:card.locked\nend\n\non click pos do\n v:get_options[]\n w:(.7*c.size[0])/count v\n set_value[v[floor(first pos)/w]]\nend","image":"%%IMG2AGQAZAD/AP8A/wD/AP8A/wCqAWUgYgECIGIBAiBiAQIgYgECIGIBAiBiAQIgYgECIGIBAiBiAQIgYgECIGIBAiBiAQIgYgECIGIBAiBiAQIgYgECIGIBAiBiAQIgYgECIGIBAiBiAQIgYgECIGIBAiBiAQIgYgECIGIBAiBiAQIgYgECIGIBAiBiAQIgYgECIGIBAiBiAQIgYgECIGIBAiBiAQIgYgECIGIBAiBiAQIgYgECIGIBAiBiAQIgYgECIGIBAiBiAQIgYgECIGIBAiBiAQIgYgECIGIBAiBiAQIgYgECIGIBAiBiAQIgYgECIGIBAiBiAQIgYgECIGIBAiBiAQIgYgECIGIBAiBiAQIgYgECIGIBAiBiAQIgYgECIGIBAiBiAQIgYgECIGIBAiBiAQIgYgECIGIBAiBiAQIgYgECIGIBAiBiAQIgYgECIGIBAiBiAWU=","attributes":{"name":["options_text"],"label":["Sources"],"type":["code"]},"widgets":{"o":{"type":"field","size":[100,20],"pos":[123,1],"show":"none","style":"plain","value":"card"},"c":{"type":"canvas","size":[100,18],"pos":[0,0],"locked":1,"volatile":1,"show":"transparent","border":0,"pattern":32,"scale":1},"i":{"type":"slider","size":[100,25],"pos":[123,32],"show":"none","interval":[0,2]},"scr":{"type":"field","size":[96,79],"pos":[2,19],"volatile":1,"border":0,"scrollbar":1,"style":"code"}}}}}
Developer(+1)

Do you like car analogies for your UI components? Indulge yourself with

The RadioButton Contraption:


RadioButtons behave like checkboxes, but only one of the RadioButtons on a card with the same .group property may have a truthy .value. This contraption respects the standard .font, .show, and .locked attributes and fires a click[] event when clicked.

%%WGT0{"w":[{"name":"radio","type":"contraption","size":[134,18],"pos":[58,82],"font":"menu","show":"transparent","def":"radioButton","widgets":{"c":{"show":"transparent"},"l":{"font":"menu","show":"transparent","value":"First"},"b":{},"g":{"value":"a"}}}],"d":{"radioButton":{"name":"radioButton","size":[134,18],"resizable":1,"margin":[20,1,4,1],"description":"a selectable button which permits only a single selection within a group.","version":1,"script":"on get_label do l.text end\non set_label x do l.text:x view[] end\non get_group do g.text end\non set_group x do g.text:x end\non get_value do b.value end\non set_value_raw x do b.value:x view[] end\ndot:image[\"%%IMG0ABAADgAAAAAAAAAAB4APwA/AD8APwAeAAAAAAAAAAAA=\"]\noutline:image[\"%%IMG2ABAADgAVAQQACgECIAQBAgAHAQEgCAEBAAYBASAIAQEABQEBIAoBAQAEAQEgCgEBAAQBASAKAQEABAEBIAoBAQAFAQEgCAEBAAYBASAIAQEABwECIAQBAgAKAQQAFw==\"]\non view do\n l.font:       card.font\n l.show:c.show:card.show\n b.locked:card.locked\n if card.locked (outline,dot)..map[1 dict 13] end # gray out\n c.clear[]\n c.paste[outline]\n if b.value c.paste[dot 0,0 1] end\nend\non set_value x do\n gr:extract value where value..group=g.text from card.parent.widgets\n if x gr.  .value_raw:0\n else gr[0].value_raw:1\n end\n set_value_raw[x]\nend\non click do\n if !card.locked\n  set_value[1]\n  card.event.click\n end\nend","template":"on click do\n \nend","attributes":{"name":["label","group"],"label":["Label","Group"],"type":["string","string"]},"widgets":{"c":{"type":"canvas","size":[16,14],"pos":[1,2],"locked":1,"volatile":1,"border":0,"scale":1},"l":{"type":"field","size":[114,16],"pos":[19,1],"locked":1,"border":0,"style":"plain"},"b":{"type":"button","size":[134,18],"pos":[0,0],"style":"invisible"},"g":{"type":"field","size":[100,20],"pos":[149,-60],"locked":1,"show":"none"}}}}}
(1 edit) (+3)

This has been useful for me so I might as well put it here.

Palette Adjuster

A sort of supplement to Palette Import. If you only want to adjust certain colors this thing will let you change them one-by-one by entering individual hexcodes. The color boxes are previews of what you'll be replacing.

Be a little careful if you want to reverse or otherwise dramatically change the 1-bit color slots. This does not have the color sorting feature that PalImport does. Decker's user interface will be very difficult to navigate if you set both of the 1-bit colors to the same hexcode. :)

%%WGT0{"w":[{"name":"PaletteAdjuster","type":"contraption","size":[316,211],"pos":[98,66],"def":"PaletteAdjuster","widgets":{"field1":{},"button99":{},"button0":{},"whitehex":{},"blackhex":{},"yellowhex":{},"button1":{},"button2":{},"button3":{},"orangehex":{},"redhex":{},"magentahex":{},"button4":{},"button5":{},"purplehex":{},"bluehex":{},"button6":{},"button7":{},"button8":{},"cyanhex":{},"greenhex":{},"darkgreenhex":{},"button9":{},"button17":{},"button18":{},"brownhex":{},"tanhex":{},"lightgrayhex":{},"button19":{},"button20":{},"mediumgrayhex":{},"darkgrayhex":{},"button21":{},"orangepreview":{},"blackpreview":{},"whitepreview":{},"yellowpreview":{},"redpreview":{},"magentapreview":{},"purplepreview":{},"bluepreview":{},"cyanpreview":{},"greenpreview":{},"darkgreenpreview":{},"brownpreview":{},"tanpreview":{},"lightgraypreview":{},"mediumgraypreview":{},"darkgray":{}}}],"d":{"PaletteAdjuster":{"name":"PaletteAdjuster","size":[316,211],"margin":[0,0,0,0],"description":"Modify each color of your palette individually.","version":1,"image":"%%IMG2ATwA0wH/Af8BfACcAQMAmAEFAJwBAwCYAQUAnAEDAJgBBQCcAQMAmAEFABIBSQBBAQMAmAEFABIBSQBBAQMAmAEFABIBAgBFAQIAQQEDAJgBBQASAQIARQECAEEBAwCYAQUAEgECAEUBAgBBAQMAmAEFABIBAgBFAQIAQQEDAJgBBQASAQIARQECAEEBAwCYAQUAEgECAEUBAgBBAQMAmAEFABIBAgBFAQIAQQEDAJgBBQASAQIARQECAEEBAwCYAQUAEgECAEUBAgBBAQMAmAEFABIBAgBFAQIAQQEDAJgBBQASAQIARQECAEEBAwCYAQUAEgECAEUBAgBBAQMAmAEFABIBAgBFAQIAQQEDAJgBBQAGAZIABAEDAJgBBQAGAZIABAEDAJgBBQAGAQIUjgECAAQBAwCYAQUABgECFI4BAgAEAQMAmAEFAAYBAhSOAQIABAEDAJgBBQAGAQIUjgECAAQBAwCYAQUABgECFI4BAgAEAQMAmAEFAAYBAhSOAQIABAEDAJgBBQAGAQIUjgECAAQBAwCYAQUABgECFI4BAgAEAQMAmAEFAAYBAhSOAQIABAEDAJgBBQAGAQIUjgECAAQBAwCYAQUABgECFI4BAgAEAQMAmAEFAAYBAhSOAQIABAEDAJgBBQAGAQIUjgECAAQBAwCYAQUABgECFI4BAgAEAQMAmAEFAAYBAhSOAQIABAEDAJgBBQAGAQIUjgECAAQBAwCYAQUABgECFI4BAgAEAQMAmAEFAAYBAhSOAQIABAEDAJgBBQAGAQIUjgECAAQBAwCYAQUABgECFI4BAgAEAQMAmAEFAAYBAhSOAQIABAEDAJgBBQAGAQIUjgECAAQBAwCYAQUABgECFI4BAgAEAQMAmAEFAAYBAhSOAQIABAEDAJgBBQAGAQIUjgECAAQBAwCYAQUABgECFI4BAgAEAQMAmAEFAAYBAhSOAQIABAEDAJgBBQAGAQIUjgECAAQBAwCYAQUABgECFI4BAgAEAQMAmAEFAAYBAhSOAQIABAEDAJgBBQAGAQIUjgECAAQBAwCYAQUABgECFI4BAgAEAQMAmAEFAAYBAhSOAQIABAEDAJgBBQAGAQIUjgECAAQBAwCYAQUABgECFI4BAgAEAQMAmAEFAAYBAhSOAQIABAEDAJgBBQAGAQIUjgECAAQBAwCYAQUABgECFI4BAgAEAQMAmAEFAAYBAhSOAQIABAEDAJgBBQAGAQIUjgECAAQBAwCYAQUABgECFI4BAgAEAQMAmAEFAAYBAhSOAQIABAEDAJgBBQAGAQIUjgECAAQBAwCYAQUABgECFI4BAgAEAQMAmAEFAAYBkgAEAQMAmAEFAAYBkgAEAQMAmAEFAJwBAwCYAQUAnAEDAJgBBQCcAQMAmAEFAJwBAwCYAQUAnAEDAJgBBQCcAQMAmAEFAJwBAwCYAQUAnAEDAJgBBQCcAQMAmAEFAJwBAwCYAQUAnAEDAJgBBQCcAQMAmAEFAJwBAwCYAQUAnAEDAJgBBQCcAQMAmAEFAJwBAwCYAQUAnAEDAJgBBQCcAQMAmAEFAJwBAwCYAQUAnAEDAJgBBQCcAQMAmAEFAJwBAwCYAQUAnAEDAJgBBQCcAQMAmAEFAJwBAwCYAQUAnAEDAJgBBQCcAQMAmAEFAJwBAwCYAQUAnAEDAJgBBQCcAQMAmAEFAJwBAwCYAQUAnAEDAJgBBQCcAQMAmAEFAJwBAwCYAQUAnAEDAJgBBQCcAQMAmAEFAJwBAwCYAQUAnAEDAJgBBQCcAQMAmAEFAJwBAwCYAQUAnAEDAJgBBQCcAQMAmAEFAJwBAwCYAQUAnAEDAJgBBQCcAQMAmAEFAJwBAwCYAQUAnAEDAJgBBQCcAQMAmAEFAJwBAwCYAQUAnAEDAJgBBQCcAQMAmAEFAJwBAwCYAQUAnAEDAJgBBQCcAQMAmAEFAJwBAwCYAQUAnAEDAJgBBQCcAQMAmAEFAJwBAwCYAQUAnAEDAJgBBQCcAQMAmAEFAJwBAwCYAQUAnAEDAJgBBQCcAQMAmAEFAJwBAwCYAQUAnAEDAJgBBQCcAQMAmAEFAJwBAwCYAQUAnAEDAJgBBQCcAQMAmAEFAJwBAwCYAQUAnAEDAJgBBQCcAQMAmAEFAJwBAwCYAQUAnAEDAJgBBQCcAQMAmAEFAJwBAwCYAQUAnAEDAJgBBQCcAQMAmAEFAJwBAwCYAQUAnAEDAJgBBQCcAQMAmAEFAJwBAwCYAQUAnAEDAJgBBQCcAQMAmAEFAJwBAwCYAQUAnAEDAJgBBQCcAQMAmAEFAJwBAwCYAQUAnAEDAJgBBQCcAQMAmAEFAJwBAwCYAQUAnAEDAJgBBQCcAQMAmAEFAJwBAwCYAQUAnAEDAJgBBQCcAQMAmAEFAJwBAwCYAQUAnAEDAJgBBQCcAQMAmAEFAJwBAwCYAQUAnAEDAJgBBQCcAQMAmAEFAJwBAwCYAQUAnAEDAJgBBQCcAQMAmAEFAJwBAwCYAQUAnAEDAJgBBQCcAQMAmAEFAJwBAwCYAQUAnAEDAJgBBQCcAQMAmAEFAJwBAwCYAQUAnAEDAJgBBQCcAQMAmAEFAJwBAwCYAQUAnAEDAJgBBQCcAQMAmAEFAJwBAwCYAQUAnAEDAJgBBQCcAQMAmAEFAJwBAwCYAQUAnAEDAJgBBQCcAQMAmAEFAJwBAwCYAQUAnAEDAJgBBQCcAQMAmAEFAJwBAwCYAQUAnAEDAJgBBQCcAQMAmAEFAJwBAwCYAQUAnAEDAJgBBQCcAQMAmAEFAJwBAwCYAQUAnAEDAJgBBQCcAQMAmAEFAJwBAwCYAQUAnAEDAJgBBQCcAQMAmAEFAJwBAwCYAf8B/wH/Abo=","attributes":{"name":[],"label":[],"type":[]},"widgets":{"field1":{"type":"field","size":[74,12],"pos":[21,8],"locked":1,"show":"transparent","border":0,"value":"1-Bit  Patterns"},"button99":{"type":"button","size":[40,18],"pos":[60,25],"script":"on click do\n  patterns[colors.white]:\"%h\" parse whitehex.text\nend","text":"Apply"},"button0":{"type":"button","size":[40,18],"pos":[60,47],"script":"on click do\n  patterns[colors.black]:\"%h\" parse blackhex.text\nend","text":"Apply"},"whitehex":{"type":"field","size":[44,18],"pos":[12,25],"style":"code","align":"center"},"blackhex":{"type":"field","size":[44,18],"pos":[12,47],"align":"center"},"yellowhex":{"type":"field","size":[44,18],"pos":[12,73],"style":"code","align":"center"},"button1":{"type":"button","size":[40,18],"pos":[60,73],"script":"on click do\n  patterns[colors.yellow]:\"%h\" parse yellowhex.text\nend","text":"Apply"},"button2":{"type":"button","size":[40,18],"pos":[60,95],"script":"on click do\n  patterns[colors.orange]:\"%h\" parse orangehex.text\nend","text":"Apply"},"button3":{"type":"button","size":[40,18],"pos":[60,117],"script":"on click do\n  patterns[colors.red]:\"%h\" parse redhex.text\nend","text":"Apply"},"orangehex":{"type":"field","size":[44,18],"pos":[12,95],"style":"code","align":"center"},"redhex":{"type":"field","size":[44,18],"pos":[12,117],"align":"center"},"magentahex":{"type":"field","size":[44,18],"pos":[12,139],"style":"code","align":"center"},"button4":{"type":"button","size":[40,18],"pos":[60,139],"script":"on click do\n  patterns[colors.magenta]:\"%h\" parse magentahex.text\nend","text":"Apply"},"button5":{"type":"button","size":[40,18],"pos":[60,161],"script":"on click do\n  patterns[colors.purple]:\"%h\" parse purplehex.text\nend","text":"Apply"},"purplehex":{"type":"field","size":[44,18],"pos":[12,161],"align":"center"},"bluehex":{"type":"field","size":[44,18],"pos":[12,183],"style":"code","align":"center"},"button6":{"type":"button","size":[40,18],"pos":[60,183],"script":"on click do\n  patterns[colors.blue]:\"%h\" parse bluehex.text\nend","text":"Apply"},"button7":{"type":"button","size":[40,18],"pos":[215,29],"script":"on click do\n  patterns[colors.cyan]:\"%h\" parse cyanhex.text\nend","text":"Apply"},"button8":{"type":"button","size":[40,18],"pos":[215,51],"script":"on click do\n  patterns[colors.green]:\"%h\" parse greenhex.text\nend","text":"Apply"},"cyanhex":{"type":"field","size":[44,18],"pos":[167,29],"style":"code","align":"center"},"greenhex":{"type":"field","size":[44,18],"pos":[167,51],"align":"center"},"darkgreenhex":{"type":"field","size":[44,18],"pos":[167,73],"script":"on change val do\n \nend","style":"code","align":"center"},"button9":{"type":"button","size":[40,18],"pos":[215,73],"script":"on click do\n  patterns[colors.darkgreen]:\"%h\" parse darkgreenhex.text\nend","text":"Apply"},"button17":{"type":"button","size":[40,18],"pos":[215,95],"script":"on click do\n  patterns[colors.brown]:\"%h\" parse brownhex.text\nend","text":"Apply"},"button18":{"type":"button","size":[40,18],"pos":[215,117],"script":"on click do\n  patterns[colors.tan]:\"%h\" parse tanhex.text\nend","text":"Apply"},"brownhex":{"type":"field","size":[44,18],"pos":[167,95],"style":"code","align":"center"},"tanhex":{"type":"field","size":[44,18],"pos":[167,117],"align":"center"},"lightgrayhex":{"type":"field","size":[44,18],"pos":[167,139],"script":"on change val do\n \nend","style":"code","align":"center"},"button19":{"type":"button","size":[40,18],"pos":[215,139],"script":"on click do\n  patterns[colors.lightgray]:\"%h\" parse lightgrayhex.text\nend","text":"Apply"},"button20":{"type":"button","size":[40,18],"pos":[215,161],"script":"on click do\n  patterns[colors.mediumgray]:\"%h\" parse mediumgrayhex.text\nend","text":"Apply"},"mediumgrayhex":{"type":"field","size":[44,18],"pos":[167,161],"align":"center"},"darkgrayhex":{"type":"field","size":[44,18],"pos":[167,183],"style":"code","align":"center"},"button21":{"type":"button","size":[40,18],"pos":[215,183],"script":"on click do\n  patterns[colors.darkgray]:\"%h\" parse darkgrayhex.text\nend","text":"Apply"},"orangepreview":{"type":"canvas","size":[46,18],"pos":[104,95],"locked":1,"image":"%%IMG2AC4AEiL/Iv8i/yI/","scale":1},"blackpreview":{"type":"canvas","size":[46,18],"pos":[104,47],"locked":1,"image":"%%IMG2AC4AEgH/Af8B/wE/","scale":1},"whitepreview":{"type":"canvas","size":[46,18],"pos":[104,25],"locked":1,"image":"%%IMG2AC4AEiD/IP8g/yA/","scale":1},"yellowpreview":{"type":"canvas","size":[46,18],"pos":[104,73],"locked":1,"image":"%%IMG2AC4AEiH/If8h/yE/","scale":1},"redpreview":{"type":"canvas","size":[46,18],"pos":[104,117],"locked":1,"image":"%%IMG2AC4AEiP/I/8j/yM/","scale":1},"magentapreview":{"type":"canvas","size":[46,18],"pos":[104,139],"locked":1,"image":"%%IMG2AC4AEiT/JP8k/yQ/","scale":1},"purplepreview":{"type":"canvas","size":[46,18],"pos":[104,161],"locked":1,"image":"%%IMG2AC4AEiX/Jf8l/yU/","scale":1},"bluepreview":{"type":"canvas","size":[46,18],"pos":[104,183],"locked":1,"image":"%%IMG2AC4AEib/Jv8m/yY/","scale":1},"cyanpreview":{"type":"canvas","size":[46,18],"pos":[259,27],"locked":1,"image":"%%IMG2AC4AEif/J/8n/yc/","scale":1},"greenpreview":{"type":"canvas","size":[46,18],"pos":[259,49],"locked":1,"image":"%%IMG2AC4AEij/KP8o/yg/","scale":1},"darkgreenpreview":{"type":"canvas","size":[46,18],"pos":[259,71],"locked":1,"image":"%%IMG2AC4AEin/Kf8p/yk/","scale":1},"brownpreview":{"type":"canvas","size":[46,18],"pos":[259,93],"locked":1,"image":"%%IMG2AC4AEir/Kv8q/yo/","scale":1},"tanpreview":{"type":"canvas","size":[46,18],"pos":[259,115],"locked":1,"image":"%%IMG2AC4AEiv/K/8r/ys/","scale":1},"lightgraypreview":{"type":"canvas","size":[46,18],"pos":[259,137],"locked":1,"image":"%%IMG2AC4AEiz/LP8s/yw/","scale":1},"mediumgraypreview":{"type":"canvas","size":[46,18],"pos":[259,161],"locked":1,"image":"%%IMG2AC4AEi3/Lf8t/y0/","scale":1},"darkgray":{"type":"canvas","size":[46,18],"pos":[259,183],"locked":1,"image":"%%IMG2AC4AEi7/Lv8u/y4/","scale":1}}}}}
(3 edits) (+4)

Ever wanted to be able to have text fields that are colours other than black or white, or indeed have white text on a transparent background?

ColourText can do that!


It's a pretty simple contraption, there's a field for rich text and a field for the desired colour (by decker palette index). It'll flow/wrap the text fine, but if you're resizing in widgets mode it won't update until you switch back to interact mode. It can't do the "editing in interact mode" thing like real text fields can nor does it have support for adding borders or being able to scroll. If you need those I'll say it's a challenge for the reader at the moment.

Give it a try though! Let me know if there's any obvious bugs or fixes I ought to do.

Edit: you may want to grab the updated version from the comments - it sets the internal canvas as "volatile" so that when you save your deck it doesn't include the image of text, thus saving on space. But it has the drawback of playing a bit funny with transition effects in a way that is workaround-able but requires some coding, so I'm leaving the original version here in case that's easier for you

%%WGT0{"w":[{"name":"colourtext1","type":"contraption","size":[134,65],"pos":[142,122],"show":"transparent","def":"colourtext","widgets":{"canvas1":{"size":[134,65],"image":"%%IMG2AIYAQQD/AJUjAQAOIwEADCMBAAkjAgABIwIABCMBAAIjAQAFIwEACSMBAA4jAQAIIwEAAiMBACgjAQAOIwEADCMBAAojAQACIwEABCMBAAIjAQAPIwEADiMBAAsjAQAoIwEABSMCAAMjAgACIwEAAiMBAAQjAgACIwMABCMCAAMjAQACIwEAAyMDAAEjAwACIwIAAiMDAAMjAwACIwIAAiMBAAMjAQABIwMABiMCAAEjAwAEIwIAAyMCAAIjAwABIwIAAyMCAAMjAwAJIwEABCMBAAIjAQABIwEAAiMBAAEjAQABIwEAByMBAAIjAQAHIwEAAiMBAAIjAQAEIwEAAiMBAAIjAQACIwEAASMBAAcjAQACIwEAAiMBAAIjAQABIwEAAyMBAAgjAQACIwEABCMBAAIjAQABIwEAAiMBAAEjAQACIwEAAiMBAAEjAQACIwEAASMBAAwjAQAEIwEAAiMBAAEjAQACIwEAASMCAAYjAwACIwEABSMDAAIjAQACIwEABCMBAAIjAQACIwEAAiMBAAIjAgAFIwEAAiMEAAMjAQAEIwEACCMBAAIjAQAEIwEABCMBAAIjAQABIwEAAiMBAAIjAQABIwQAAiMCAAojAQAEIwEAAiMBAAEjAQACIwEAASMBAAEjAQAEIwEAAiMBAAIjAQAEIwEAAiMBAAIjAQACIwEABCMBAAIjAQACIwEAAiMBAAQjAQAEIwEAAiMBAAUjAQABIwEAAyMBAAgjAQACIwEABCMBAAIjAQABIwEAAiMBAAEjAQACIwEAAiMBAAEjAQAHIwEACSMEAAIjAgADIwIAAiMBAAIjAQAEIwMAAyMBAAQjAwACIwEAAiMBAAUjAQABIwEAAiMBAAIjAQABIwMABiMBAAIjAgACIwEAAyMBAAMjAQACIwEABCMBAAMjAQAEIwIAAyMCAAIjAQACIwEAAiMBAAIjAgACIwMAXyMBAIQjAQDFIwIAASMCAAMjAwADIwMAHSMCAAcjAwAQIwIALSMBAAwjAgAFIwIABCMCAB8jAgAGIwIAEiMCABcjAQAfIwUAASMCAAEjBAACIwQAAyMEAAIjAgABIwIAAiMEAAIjBQACIwQABCMEAAMjBAACIwUAAiMEAAIjAwARIwEAFCMCAAEjAwAEIwIAAiMCAAEjAgACIwIABCMCAAMjAgACIwIAASMDAAMjAgACIwIAASMCAAIjAgACIwIABiMCAAMjAgACIwIAASMCAAIjAgACIwIAAiMCAAIjAQAEIwIAAiMDAAMjAwAVIwEAASMBAAIjAQADIwIAAiMCAAEjAgACIwIABCMCAAMjAgACIwIAASMCAAQjAgACIwIAASMCAAIjAgACIwIABiMCAAMjAgACIwIAASMCAAIjAgACIwIAAiMDAAgjAQABIwEAAiMBAAEjAQACIwEAFSMBAAEjAQACIwEAAyMCAAIjAgABIwIAAiMCAAQjAgADIwYAASMCAAQjBgABIwIAAiMCAAIjAgAGIwIAAyMCAAIjAgABIwIAAiMCAAIjAgADIwMABSMDAAEjAQACIwEAASMBAAIjAQAVIwEAASMBAAIjAQADIwIAAiMCAAEjAgACIwIABCMCAAMjAgAFIwIABCMCAAUjAgACIwIAAiMCAAYjAgADIwIAAiMCAAEjAgACIwIAAiMCAAQjAwADIwEAAiMBAAEjAQACIwEAASMBAAIjAQAVIwEAASMBAAIjAQADIwIAAiMCAAEjAgACIwIABCMCAAMjAgADIwEAASMCAAQjAgADIwEAASMCAAIjAgACIwIABiMCAAMjAgACIwIAASMCAAIjAgACIwIAAiMBAAIjAgAEIwMAASMBAAIjAQACIwMAHyMFAAEjAgACIwIABCMCAAQjBAACIwIABSMEAAIjAgACIwIAAyMCAAUjAgAEIwQAAiMCAAIjAgADIwIAAiMDAP8A/wBBIwEAAiMBAAIjAQAVIwEAECMBAAQjAQAwIwEAJSMBAAIjAQAVIwEAECMBAAQjAQAwIwEAISMCAAEjAwAEIwMAAyMBAAEjAgACIwIAAyMDAAQjAgACIwMAAyMDAAMjAwABIwEAASMCAAIjAgACIwMAAyMDAAEjAwADIwIAAiMBAAEjAgACIwIAAiMDAAIjAwAhIwEAAiMBAAQjAQAGIwIAAyMBAAIjAQABIwEAAiMBAAYjAQABIwEAAiMBAAEjAQACIwEABCMBAAIjAgAGIwEAASMBAAIjAQABIwEABCMBAAIjAQAEIwEAASMCAAMjAQACIwEAASMBAAIjAQACIwEAIiMBAAIjAQAFIwIABCMBAAQjBAABIwEAAiMBAAQjAwABIwEAAiMBAAEjAQACIwEABCMBAAIjAQAFIwMAASMBAAIjAQACIwIAAiMBAAIjAQACIwMAASMBAAQjBAABIwEAAiMBAAIjAQAiIwEAAiMBAAcjAQADIwEABCMBAAQjAQACIwEAAyMBAAIjAQABIwEAAiMBAAEjAQACIwEABCMBAAIjAQAEIwEAAiMBAAEjAQACIwEABCMBAAEjAQACIwEAASMBAAIjAQABIwEABCMBAAQjAQACIwEAAiMBACIjAQADIwEAAyMDAAQjAQAFIwIAAyMDAAQjAwABIwEAAiMBAAIjAwAFIwEAASMBAAUjAwABIwEAAiMBAAEjAwACIwMAAyMDAAEjAQAFIwIAAiMBAAIjAQADIwEAaiMBAIUjAQD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD8","pattern":35},"field1":{"pos":[-186,154],"value":{"text":["Look at all this text, it comes in ","different fonts"," and it's red and transparent"],"font":["","menu",""],"arg":["","",""]}}}}],"d":{"colourtext":{"name":"colourtext","size":[100,100],"resizable":1,"margin":[0,0,0,0],"description":"Like a text box, but with colour!","version":1,"script":"on get_colour do\n canvas1.pattern\nend\n\non set_colour x do\n canvas1.pattern:x\n redraw[]\nend\n\non get_value do\n field1.value\nend\n\non set_value x do\n field1.value:x\n redraw[]\nend\n\non redraw do\n canvas1.clear[]\n size:canvas1.textsize[field1.value canvas1.size[0]-4]\n canvas1.text[field1.value 2,2,size]\nend\n\non view do\n redraw[]\nend","attributes":{"name":["value","colour"],"label":["Text","Colour (palette index)"],"type":["rich","number"]},"widgets":{"canvas1":{"type":"canvas","size":[100,100],"pos":[0,0],"locked":1,"show":"transparent","border":0,"scale":1},"field1":{"type":"field","size":[100,20],"pos":[-186,189]}}}}}
(+2)

Ooh this will come in handy!! :D

Developer(+2)

One possible refinement to this which would produce smaller deck sizes would be to mark the internal canvas as "volatile"; instead of saving the rendered bitmap of text, Decker would then regenerate the image at load time or the first view[] event. (This would also require you to save the pattern selection in a separate internal field.)

(+2)

Thanks, I have yet to get my head around "volatile" but I will experiment. I was hesitant to try because I wasn't sure whether it would play nice with transition functions, e.g. would it only call the "view" at the end of the transition?

I will experiment and see if I can get it working, since not having images saved in the deck files is probably nicer

(1 edit) (+2)

So I whipped up a version that uses volatiles, but it does indeed have the problem of, if you're transitioning between cards on a freshly opened deck, it doesn't draw the text in until the end of the transition, so it kind of "pops in". I've uploaded a quick example to show what I mean https://zine.milliesquilly.com/surprises/colourtextpopindemo.html

If there's something I'm missing here please let me know

Here's the new version of the contraption - I'm hestitant to replace the original since it's not a strict improvement, but if you're not using transitions or if you don't mind manually calling "view" on the contraption before the transition it does save on deck size

Edit: slightly updated version, did some experimenting and wasn't able to get manually calling "view" to work so I've exposed the redraw[] function itself. So if you want to use this version and need to refresh the contents before a transition (or whatever other fun things you're doing) you can call redraw[] on the widget to refresh it before your transition.

%%WGT0{"w":[{"name":"colourtext1","type":"contraption","size":[134,65],"pos":[145,125],"show":"transparent","def":"colourtext","widgets":{"canvas1":{"size":[134,65],"pattern":35},"field1":{"pos":[-186,154],"value":{"text":["Look at all this text, it comes in ","different fonts"," and it's red and transparent"],"font":["","menu",""],"arg":["","",""]}},"slider1":{"size":[116,25],"pos":[-52,149],"value":35}}}],"d":{"colourtext":{"name":"colourtext","size":[100,100],"resizable":1,"margin":[0,0,0,0],"description":"Like a text box, but with colour! With volatiles now. Exposes a redraw[] function if you need to refresh it manually for whatever reason.","version":1.2,"script":"on get_colour do\n slider1.value\nend\n\non set_colour x do\n slider1.value:x\n redraw[]\nend\n\non get_value do\n field1.value\nend\n\non set_value x do\n field1.value:x\n redraw[]\nend\n\non redraw do\n canvas1.clear[]\n canvas1.pattern:slider1.value\n size:canvas1.textsize[field1.value canvas1.size[0]-4]\n canvas1.text[field1.value 2,2,size]\nend\n\non view do\n redraw[]\nend\n\non get_redraw do\n redraw\nend","attributes":{"name":["value","colour"],"label":["Text","Colour (palette index)"],"type":["rich","number"]},"widgets":{"canvas1":{"type":"canvas","size":[100,100],"pos":[0,0],"locked":1,"volatile":1,"show":"transparent","border":0,"scale":1},"field1":{"type":"field","size":[100,20],"pos":[-186,189]},"slider1":{"type":"slider","size":[100,25],"pos":[-52,184],"interval":[0,47],"value":1,"style":"compact"}}}}}
(1 edit) (+1)

I have come up with a... sort of solution for drawing all the colour text at deck launch so that I don't have the "pop in after transition" issue

Essentially I've just put this function in my deck-level scripts to just cycle through all the cards and make them all draw on launch. Then I just call it from the "on view" of my starting card.

(removed, see below for better version)

This is kinda hacky (I didn't see an obvious way to check that the contraptions belong to the right prototype so I'm running it on any contraption) so if you have other contraptions with a redraw[] method that does something else this may not be good but it is working for my purposes!

(Apologies if there IS an obvious way to check which prototype a contraption belongs to and I'm just too eepy to figure it out right now)


Edit: better version, thanks IJ for the tip on picking the prototype name

on refreshcolourtext do
 each c in deck.cards
  each w in c.widgets
   if w.def.name="colourtext"
    w.redraw[]
   end
  end
 end
end
Developer(+2)

If you have a contraption x, you can obtain the contraption's prototype as "x.def" and the name of the contraption's prototype as "x.def.name"

(+2)

Thank you! I have improved it!

(+2)

Wow, thank you! Text color support in rich text has been something I was wishing for for a while.

(+2)

Hopefully the limitations aren't too much of an issue for you!