123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289 |
- package vdom
- import "kumachan/standalone/ctn"
- type Node struct {
- Tag string
- Props
- Content
- }
- type Props struct {
- Attrs *Attrs
- Styles *Styles
- Events *Events
- }
- type Attrs struct { Data ctn.Map[string,string] }
- type Styles struct { Data ctn.Map[string,string] }
- type Events struct { Data ctn.Map[string,*EventHandler] }
- type EventHandler struct { Handler interface{} }
- type Content interface { impl(Content) }
- func(*Text) impl(Content) {}
- func(*Children) impl(Content) {
- }
- type Text string
- type Children ([] *Node)
- type DeltaNotifier struct {
- ApplyStyle func(id string, key string, value string)
- EraseStyle func(id string, key string)
- SetAttr func(id string, key string, value string)
- RemoveAttr func(id string, key string)
- AttachEvent func(id string, name string, handler *EventHandler)
- ModifyEvent func(id string, name string)
- DetachEvent func(id string, name string, handler *EventHandler)
- SetText func(id string, content string)
- AppendNode func(parent string, id string, tag string)
- RemoveNode func(parent string, id string)
- UpdateNode func(old_id string, new_id string)
- ReplaceNode func(parent string, old_id string, new_id string, tag string)
- SwapNode func(parent string, a string, b string)
- MoveNode func(parent string, id string, pivot string)
- }
- func Diff(ctx *DeltaNotifier, parent *Node, old *Node, new *Node) {
- assert(ctx != nil)
- assert(old != nil || new != nil)
- if old == new { return }
- var parent_id = get_addr(parent)
- var old_id = get_addr(old)
- var new_id = get_addr(new)
- if old == nil {
- ctx.AppendNode(parent_id, new_id, new.Tag)
- } else if new == nil {
- detach_all_events(ctx, old)
- ctx.RemoveNode(parent_id, old_id)
- } else {
- if (old.Tag == new.Tag) {
- ctx.UpdateNode(old_id, new_id)
- } else {
- ctx.ReplaceNode(parent_id, old_id, new_id, new.Tag)
- old = nil
- }
- }
- if new != nil {
- var id = new_id
- var node = new
- var new_styles = new.Styles
- if old != nil {
- var old_styles = old.Styles
- if new_styles == old_styles {
- goto skip_styles
- }
- old_styles.Data.ForEach(func(key string, _ string) {
- if !(new_styles.Data.Has(key)) {
- ctx.EraseStyle(id, key)
- }
- })
- }
- new_styles.Data.ForEach(func(key string, val string) {
- if old != nil {
- var old_val, exists = old.Styles.Data.Lookup(key)
- if exists {
- if exists {
- if old_val == val {
- goto skip_this_style
- }
- }
- }
- }
- ctx.ApplyStyle(id, key, val)
- skip_this_style:
- })
- skip_styles:
- var new_attrs = new.Attrs
- if old != nil {
- var old_attrs = old.Attrs
- if new_attrs == old_attrs {
- goto skip_attrs
- }
- old_attrs.Data.ForEach(func(name string, _ string) {
- if !(new_attrs.Data.Has(name)) {
- ctx.RemoveAttr(id, name)
- }
- })
- }
- new_attrs.Data.ForEach(func(name string, new_val string) {
- if old != nil {
- var old_val, exists = old.Attrs.Data.Lookup(name)
- if exists {
- if old_val == new_val {
- goto skip_this_attr
- }
- }
- }
- ctx.SetAttr(id, name, new_val)
- skip_this_attr:
- })
- skip_attrs:
- var new_events = new.Events
- if old != nil {
- var old_events = old.Events
- if old_events == new_events {
- goto skip_events
- }
- old_events.Data.ForEach(func(name string, old_handler *EventHandler) {
- var new_handler, name_in_new = new_events.Data.Lookup(name)
- if name_in_new {
- if new_handler == old_handler {
- goto skip_this_event
- } else {
- ctx.DetachEvent(id, name, old_handler)
- ctx.AttachEvent(id, name, new_handler)
- }
- } else {
- ctx.DetachEvent(id, name, old_handler)
- }
- skip_this_event:
- })
- }
- new_events.Data.ForEach(func(new_name string, handler *EventHandler) {
- if !(old != nil && old.Events.Data.Has(new_name)) {
- var new_handler = handler
- ctx.AttachEvent(id, new_name, new_handler)
- }
- })
- skip_events:
- if old != nil && old.Content == new.Content {
- goto skip_content
- }
- switch new_content := new.Content.(type) {
- case *Text:
- if old != nil {
- var old_content, is_text = old.Content.(*Text)
- if is_text && (*old_content == *new_content) {
- goto skip_text
- }
- }
- ctx.SetText(id, string(*new_content))
- skip_text:;
- case *Children:
- var new_children = *new_content
- var diff_children = func(old_children Children, new_children Children) {
- var build_index = func(nodes Children) (map[*Node] int, func(*Node) bool) {
- var index = make(map[*Node] int)
- for i, node := range nodes {
- index[node] = i
- }
- var has = func(node *Node) bool {
- var _, exists = index[node]
- return exists
- }
- return index, has
- }
- var old_index, old_has = build_index(old_children)
- var _, new_has = build_index(new_children)
- var old_skip = make(map[int] bool)
- var i = 0
- var j = 0
- for (i < len(old_children)) || (j < len(new_children)) {
- if old_skip[i] {
- i += 1
- continue
- }
- var old_child *Node = nil
- var new_child *Node = nil
- if i < len(old_children) {
- old_child = old_children[i]
- }
- if j < len(new_children) {
- new_child = new_children[j]
- }
- if new_child != old_child && old_has(new_child) {
- var node_id = get_addr(node)
- var old_child_id = get_addr(old_child)
- var new_child_id = get_addr(new_child)
- old_skip[old_index[new_child]] = true
- ctx.MoveNode(node_id, new_child_id, old_child_id)
- if !(new_has(old_child)) {
- Diff(ctx, node, old_child, nil) // remove
- i += 1
- }
- j += 1
- continue
- } else if new_child != old_child && new_has(old_child) {
- var node_id = get_addr(node)
- var old_child_id = get_addr(old_child)
- var new_child_id = get_addr(new_child)
- Diff(ctx, node, nil, new_child) // append
- ctx.MoveNode(node_id, new_child_id, old_child_id)
- j += 1
- continue
- } else {
- Diff(ctx, node, old_child, new_child)
- }
- i += 1
- j += 1
- }
- }
- if old != nil {
- switch old_content := old.Content.(type) {
- case *Text:
- ctx.SetText(id, string(""))
- diff_children(Children([] *Node {}), new_children)
- case *Children:
- var old_children = *old_content
- diff_children(old_children, new_children)
- }
- } else {
- diff_children(Children([] *Node {}), new_children)
- }
- default:
- panic("impossible branch")
- }
- skip_content:
- }
- }
- func detach_all_events(ctx *DeltaNotifier, node *Node) {
- var node_id = get_addr(node)
- node.Events.Data.ForEach(func(name string, handler *EventHandler) {
- ctx.DetachEvent(node_id, name, handler)
- })
- var children, has_children = node.Content.(*Children)
- if has_children {
- for _, child := range *children {
- detach_all_events(ctx, child)
- }
- }
- }
- var emptyAttrs = &Attrs { ctn.MakeMap[string,string](ctn.StringCompare) }
- var emptyStyles = &Styles { ctn.MakeMap[string,string](ctn.StringCompare) }
- var emptyEvents = &Events { ctn.MakeMap[string,*EventHandler](ctn.StringCompare) }
- var emptyContent = &Children {}
- func EmptyAttrs() *Attrs { return emptyAttrs }
- func EmptyStyles() *Styles { return emptyStyles }
- func EmptyEvents() *Events { return emptyEvents }
- func EmptyContent() Content { return emptyContent }
- func (attrs *Attrs) MergedWith(another *Attrs) *Attrs {
- const class = "class"
- var draft = attrs.Data
- another.Data.ForEach(func(key string, another_value string) {
- var value = (func() string {
- if key == class {
- var this_value, exists = draft.Lookup(class)
- if exists {
- var merged_value = (this_value + " " + another_value)
- return merged_value
- } else {
- return another_value
- }
- } else {
- return another_value
- }
- })()
- draft = draft.Inserted(key, value)
- })
- return &Attrs { draft }
- }
- func (styles *Styles) MergedWith(another *Styles) *Styles {
- return &Styles { styles.Data.MergedWith(another.Data) }
- }
- func (events *Events) MergedWith(another *Events) *Events {
- return &Events { events.Data.MergedWith(another.Data) }
- }
|