#elembic: accessing field of enclosing element

89 messages · Page 1 of 1 (latest)

oak pebble
#

(code example in the next message)

basically, I have two elements where one is always enclosed in the other, and the enclosed element should use settings of the parent. I guess this is superficially similar to grid/grid.cell's relationship?

I found Making more context available with contextual: true, but that only works for set rules, not direct element parameters. Is there a way around this?

#

?r

#import "@preview/elembic:1.1.0" as e

#let parent = e.element.declare(
  "parent",
  prefix: "@local,v1",

  display: it => {
    [parent(#repr(it.name), #it.body)]
  },

  fields: (
    e.field("name", str, named: true),
    e.field("body", content, required: true),
  ),
)

#let child = e.element.declare(
  "child",
  prefix: "@local,v1",
  contextual: true,

  display: it => {
    let parent-name = (e.ctx(it).get)(parent).name
    [child(#repr(it.name) in #repr(parent-name))]
  },

  fields: (
    e.field("name", str, named: true),
  ),
)

#show: e.set_(parent, name: "default-parent")

#parent(name: "p", child(name: "c"))
upper groveBOT
oak pebble
#

Here the parent's name is "p", but (e.ctx(it).get)(parent).name returns "default-parent", which is what was configured with e.set_()

#

?r this here seems to work if the field is set explicitly:

#import "@preview/elembic:1.1.0" as e

#let child = e.element.declare(
  "child",
  prefix: "@local,v1",
  contextual: true,

  display: it => {
    [child(#repr(it.name) in #repr(it.parent-name))]
  },

  fields: (
    e.field("parent-name", str, named: true),
    e.field("name", str, named: true),
  ),
)

#let parent = e.element.declare(
  "parent",
  prefix: "@local,v1",

  template: it => {
    show: e.set_(child, parent-name: e.fields(it).name)
    it
  },

  display: it => {
    [parent(#repr(it.name), #it.body)]
  },

  fields: (
    e.field("name", str, named: true),
    e.field("body", content, required: true),
  ),
)

#show: e.set_(parent, name: "default-parent")

#parent(name: "p", child(name: "c"))
oak pebble
#

... but I have the feeling using e.fields() in template is not intended, since set rules are not resolved there. If I write

#parent(child(name: "c"))

I get an error that name is not defined

fringe zephyr
#

lol

fringe zephyr
#

so you cant really do this

#

what grid does is basically create a new internal Cell structure based on the set-rule-resolved properties of each grid cell

#

i.e. it never places the cell explicitly

#

so there are two options

#

one of them is to do it like grid, but that's not very simple to do with elembic atm

#

another is to use a set rule in your parent

#

say, set child(parent-data: (...))

#

and then the latest one is available as a field

#

i'd probably go for that

#

also

#

honestly contextual: true has a bit of unclear semantics right now

#

so i might remove that from the docs

#

sorry for the confusion

#

i think that page i just linked to will be clearer

fringe zephyr
#

worth saying

#

i.e. im assuming you will just place them elsewhere

#

otherwise you'd need to resolve fields anyway... which cant be accurately done in elembic right now (you can resolve set rules and manually fold them with args, but this still ignores cond-set)
(though i guess ignoring cond-set is a bit of a "minor" problem in the grand scheme of things)

oak pebble
#

ok, guess I still have a lot to learn 😛

oak pebble
fringe zephyr
#

but since this set rule is internal, it shouldnt go in template anyway

#

you only place set rules in template which you want users to be able to override

#

basically

oak pebble
fringe zephyr
#

yeah

#

for this you can mark the parent-name field as internal with internal: true

#

though thats purely cosmetic

#

:p

#

since it can still be specified

#

but i guess it's better to lay out your intentions explicitly

oak pebble
#

ok, gonna try and apply these hints now 🙂

fringe zephyr
#

im slowly working to make it all more consistent so dont mind the mess right now :p

#

the reason template does not resolve set rules is that i wanted you to be able to apply something before the element even resolves stuff

#

but i think you'd just use a custom constructor for that anyway...

#

and the whole e.selector(outer: ...) thing , i think it's just more confusing in the end

#

there is practical use for it but i probably wont display it in the docs anymore

#

sometimes less is more 🙂

#

fwiw a lot of the api design choices so far were me thinking too much about performance rather than usability...

#

:p

#

which should hopefully make it easier to apply show-set on elements

oak pebble
#

fwiw, I'm super impressed by elembic. I was writing a question yesterday and halfway through thought I'd better look a bit closer in the docs. Turns out I was looking for template 😛

The outer is something I will probably only look at if I think it may be relevant, it does sound niche.

And for usability vs ergonomics: I guess some consideration for performance is necessary to make people interested in using it, and getting usage experience is the point. So fair enough -- if some of the algorithms turn out useful for native custom types, even better 🙂

fringe zephyr
fringe zephyr
#

the outer selector basically is a selector around the whole element, before anything is resolved, so applying show-set on it would allow not needing an additional context {} in display to read what you set

#

but like usually that doesnt matter that much so i'd say this should be an exception and not a rule

#

which is why i want to keep it as more of a footnote in the docs or something

#

should only matter if you are placing hundreds of elements or something

#

Turns out I was looking for template 😛
since im considering changing it a bit, mind telling me what this was about? 👀

#

perhaps it was just that you needed overridable set rules?

oak pebble
#

In another case I opted for giving my element its own stroke setting and applying that in display, since I'd need to apply the stroke to two different elements.

fringe zephyr
#

and you cant do that in template since you need fields

#

tbh i think template would be more generally useful if i just moved it to after resolving things

#

but ill think about it , i could also create a separate show-set setting which is just template but with fields lol

#

the downside of doing that is that you wont be able to apply a set rule on the element itself there

#

but i'd say you should use default field values for that

oak pebble
#

actually, in case it's useful to think about different use cases, I have a fairly special interdependence of fields on my type:
classifiers can be of different kinds, such as class or interface. The can also have stereotypes such as «annotation», and can be abstract.
Now there are the following dependencies:

  • a classifier of kind interface automatically gets the stereotype «interface» (because interface is both a kind and stereotype)
  • if abstract is set to auto, it should default to false except for interfaces which are presumed to be abstract (even then I allow setting abstract: false even though it's strictly speaking nonsensical)
  • then I put a name element into my classifier. If the classifier is abstract, I set show name: set text(style: "italic")

I currently implement that like this:

template: it => {
  let fields = e.fields(it)

  let kind = fields.at("kind", default: "class")
  let abstract = fields.at("abstract", default: auto)
  if abstract == auto { abstract = kind == "interface" }

  show: if abstract {
    e.show_(name, it => { set text(style: "italic"); it })
  } else {
    it => it
  }

  it
},

(this is definitely one of those cases where resolved fields would be nicer. I'm duplicating the defaults specified in fields here...)

I definitely don't expect elembic to cater to these exact needs, but maybe it gives you some context what needs there could be 😛

fringe zephyr
#

yeah i think template with resolved fields would be useful here

#

still thinking about how exactly that might look like

#

i wonder if i should call that something like overridable

#

lol

oak pebble
#

fyi, the experiment went well 🙂 https://github.com/SillyFreak/typst-plum/tree/elembic

the dark mode thumbnail now uses elembic to set a white stroke on classifiers -- edges not yet, I have to take a look how I can usefully customize the fletcher edges -- and I can e.g. highlight certain classes without modifying the diagram construction.

If you're motivated to take a look and give feedback, I'd be very grateful - but absolutely no pressure!

GitHub

Create UML class diagrams in Typst; inspired by but not compatible with PlantUML - GitHub - SillyFreak/typst-plum at elembic

fringe zephyr
#

i should definitely try to make the template changes soon enough

#

but for the time being it looks all good

#

maybe ill mention that

#
    show: if abstract {
      e.show_(name, it => { set text(style: "italic"); it })
    } else {
      it => it
    }

is probably better realized as show e.selector(name): set text(style: "italic") if abstract rn

#

cuz show rules have the "opposite order" problem

#

but i doubt this will make a difference in your case anyway

#

in the future i will add a native-set rule which should supersede this

#

btw, you can use e.types.option(content)

#

instead of union(none, content)