Sunday, June 23, 2024

Glimmer DSL for LibUI 0.12.0 Custom Control Component Slots

Glimmer DSL for LibUI (Prerequisite-Free Ruby Desktop Development Cross-Platform Native GUI Library) version 0.12.0 ships with an exciting new feature called Custom Control Component Slots!!! Component Slots are containers that could accept content in different parts of a Custom Control component (e.g. an address_form Custom Control can have a header slot and a footer slot to display extra information about the address being entered, which consumers could fill in with any GUI controls). A new example has been implemented to demonstrate this feature: Class-Based Custom Control Slots

Of course, Glimmer implements Component Slots with the simplest most declarative DSL syntax possible (as with all features of Glimmer), which does not require redundant declarations of slots in advance or a distinction between singular and plural slots, yet all slots are treated uniformly in the simplest way possible. In the past, if you opened a block in front of a Custom Control keyword and shoved content in it (e.g. `address_form { label('some text') }`), it got added to the bottom inside the Custom Control's top-level control (though there was a bug in that feature that was fixed in version 0.11.10). That was a good smart default as it allowed contributing Custom Control content by consumers without having to explicitly/manually specify where to insert `children` in the Custom Control. Glimmer just knew what to do by default. That said, it did not address all needs, like wanting to insert content in arbitrary places deep in a Custom Control's body hierarchy. Now, Software Engineers could allow consumers of Custom Controls to contribute content everywhere inside a Custom Control, not just inside the top-level control, with a very minimalistic declarative DSL syntax that greatly facilitates building highly customizable Custom Controls.

I leave you with the Custom Control Component Slots feature documentation below.

Glimmer on!!!

P.S. In other news, somebody built Go in Ruby using Glimmer DSL for LibUI.

Class-Based Custom Control Slots

Component can have Component Slots inside layout container controls: vertical_box, horizontal_box, form, and grid.

Simply designate a layout container control as a Component Slot inside a Custom Control class body block by passing it a slot: slot_name option (in the example below, we have a :header slot and a :footer slot):

  body {
    vertical_box {
      vertical_box(slot: :header) {
        stretchy false
      }
      form {
        form_field(model: address, attribute: :street)
        form_field(model: address, attribute: :p_o_box)
        form_field(model: address, attribute: :city)
        form_field(model: address, attribute: :state)
        form_field(model: address, attribute: :zip_code)
      }
      vertical_box(slot: :footer) {
        stretchy false
      }
    }
  }

Next, in the Custom Control consuming code, open a block matching the name of the Component Slot (e.g. header {} and footer {}):

          address_form(address: @address) {
            header {
              label('Billing Address') {
                stretchy false
              }
            }
            footer {
              label('Billing address is used for online payments') {
                stretchy false
              }
            }
          }

Note that the slotted labels can include properties that apply to their Component Slot container like stretchy false.

glimmer-dsl-libui-mac-class-based-custom-control-slots.png

Example (Class-Based Custom Control Slots):

require 'glimmer-dsl-libui'
require 'facets'

Address = Struct.new(:street, :p_o_box, :city, :state, :zip_code)

class FormField
  include Glimmer::LibUI::CustomControl
  
  options :model, :attribute
  
  body {
    entry { |e|
      label attribute.to_s.underscore.split('_').map(&:capitalize).join(' ')
      text <=> [model, attribute]
    }
  }
end

class AddressForm
  include Glimmer::LibUI::CustomControl
  
  option :address
  
  body {
    vertical_box {
      vertical_box(slot: :header) {
        stretchy false
      }
      form {
        form_field(model: address, attribute: :street)
        form_field(model: address, attribute: :p_o_box)
        form_field(model: address, attribute: :city)
        form_field(model: address, attribute: :state)
        form_field(model: address, attribute: :zip_code)
      }
      vertical_box(slot: :footer) {
        stretchy false
      }
    }
  }
end

class LabelPair
  include Glimmer::LibUI::CustomControl
  
  options :model, :attribute, :value
  
  body {
    horizontal_box {
      label(attribute.to_s.underscore.split('_').map(&:capitalize).join(' '))
      label(value.to_s) {
        text <= [model, attribute]
      }
    }
  }
end

class AddressView
  include Glimmer::LibUI::CustomControl
  
  options :address
  
  body {
    vertical_box {
      vertical_box(slot: :header) {
        stretchy false
      }
      address.each_pair do |attribute, value|
        label_pair(model: address, attribute: attribute, value: value)
      end
    }
  }
end

class ClassBasedCustomControlSlots
  include Glimmer::LibUI::Application # alias: Glimmer::LibUI::CustomWindow
  
  before_body do
    @address1 = Address.new('123 Main St', '23923', 'Denver', 'Colorado', '80014')
    @address2 = Address.new('2038 Park Ave', '83272', 'Boston', 'Massachusetts', '02101')
  end
  
  body {
    window('Class-Based Custom Control Slots') {
      margined true
      
      horizontal_box {
        vertical_box {
          address_form(address: @address1) {
            header {
              label('Shipping Address') {
                stretchy false
              }
            }
            footer {
              label('Shipping address is used for mailing purchases') {
                stretchy false
              }
            }
          }
          
          horizontal_separator {
            stretchy false
          }
          
          address_view(address: @address1) {
            header {
              label('Shipping Address (Saved)') {
                stretchy false
              }
            }
          }
        }
        
        vertical_separator {
          stretchy false
        }
        
        vertical_box {
          address_form(address: @address2) {
            header {
              label('Billing Address') {
                stretchy false
              }
            }
            footer {
              label('Billing address is used for online payments') {
                stretchy false
              }
            }
          }
          
          horizontal_separator {
            stretchy false
          }
                    
          address_view(address: @address2) {
            header {
              label('Billing Address (Saved)') {
                stretchy false
              }
            }
          }
        }
      }
    }
  }
end

ClassBasedCustomControlSlots.launch

glimmer-dsl-libui-mac-class-based-custom-control-slots.png


No comments: