2 Replies Last post: May 23, 2008 6:16 AM by Kamal

Need more details on Standard<Control>UI

May 21, 2008 11:24 PM

Click to view URPradhan's profile BlackBelt URPradhan 167 posts since
Mar 6, 2008

Hi Expers


Can anyone put some more light on "Standard<Control>UI" classes ?

How to make use of this classes to override the graphical represenation of UI controls and what things need to be taken care when we are overriding the default graphical representation of such controls ?

If you can put a simple example also it will help us to learn it more deeply.

//Thank you

Click to view Duke's profile Curl Duke 179 posts since
Oct 17, 2007
1. Re: Need more details on Standard<Control>UI May 22, 2008 11:47 AM
If you look in the documentation chapter "Building Custom Control UIs" (look at index entry "register-ui" to find it easily), you will find related information. Here is an example from the documentation.

{define-class public EllipticalCommandButtonUI {inherits CommandButtonUI}

  || Gradient FillPatterns for the unpressed and pressed button
  field private _inner-up-right-gfp:FillPattern =
      {uninitialized-value-for-type FillPattern}
  field private _inner-down-right-gfp:FillPattern =
      {uninitialized-value-for-type FillPattern}

  || Graphical structure for managing label
  field private _label-box:#Graphic
  field private _ellipse:EllipseGraphic = {EllipseGraphic}
  field private _label-shifted-forward?:bool = false

  {constructor public {default
                          control:#CommandButton=null,
                          ...
                      }
    {construct-super control=control, ...}
    {self.set-gradients self.control-color}
  }
  || notice when control color changes and update self appropriately
  {nonlocal-option public control-color:FillPattern
    {self.set-gradients control-color}
    {self.request-draw}
  }
  {method public {draw renderer2d:Renderer2d}:void
    let bounds:GRect = {self.layout.get-bounds}

    let top-edge:Distance    = -bounds.ascent
    let bottom-edge:Distance = bounds.descent
    let left-edge:Distance   = -bounds.lextent
    let right-edge:Distance  = bounds.rextent

    let w:Distance = right-edge  - left-edge
    let h:Distance = bottom-edge - top-edge

    || render the outer ellipse in gradient of control-color
    {renderer2d.render-ellipse
        left-edge,
        top-edge,
        w,h,
        fill-pattern = self._inner-up-right-gfp
    }
    || Note super.draw is necessary for the graphical children
    || (i.e.the label) to be drawn
    {super.draw renderer2d}

  }
  ||----------------------------------------
  || Methods inherited from CommandButtonUI
  ||----------------------------------------
  {method public {draw-as-normal}:void
    set self._ellipse.fill-color = self._inner-up-right-gfp
    {self.shift-label forward?=false}
    {self.request-draw}
  }
  {method public {draw-as-disabled}:void
    set self._ellipse.fill-color = self._inner-up-right-gfp
    {self.shift-label forward?=false}
    {self.request-draw}
  }
  {method public {draw-as-pressed}:void
    set self._ellipse.fill-color = self._inner-down-right-gfp
    {self.shift-label forward?=true}
    {self.request-draw}
  }
  {method public {draw-as-rollover}:void
    set self._ellipse.fill-color = self._inner-up-right-gfp
    {self.shift-label forward?=true}
    {self.request-draw}
  }
  {method public {modify-for-focus focus?:bool}:void
    {if focus? then
        set self._ellipse.color = {FillPattern.get-black}
     else
        set self._ellipse.color = self.control-color
    }
    {self.request-draw}
  }
  {method public {modify-for-default default?:bool}:void
  }
  {setter public {control control:Control}:void
    set super.control = control
    {self.setup-label}
  }
  {method public {react-to-visual-change}:void
    {self.setup-label}
  }
  ||-----------------------------------------------
  || Private utility methods
  ||----------------------------------------------
  {method private {shift-label forward?:bool=true}
    ||if label is already shifted appropriately, do nothing
    {if forward? == self._label-shifted-forward? then
        {return}
    }
    ||otherwise, shift label appropriately by pixel
    let lc:LayoutContext = {self.get-layout-context}
    let shift-distance:Distance = lc.layout-display-context.pixel-size
    {if not forward? then
        set shift-distance = -shift-distance
    }
    {self.child.shift-x-origin shift-distance, layout-context=lc}
    {self.child.shift-y-origin shift-distance, layout-context=lc}

    set self._label-shifted-forward? = not self._label-shifted-forward?
  }
  {method private {set-gradients bc:FillPattern}:void

    || set gradient patterns for the button up and down states
    set self._inner-up-right-gfp =
        {RadialGradientFillPattern
            {Spectrum.from-endpoints "white", bc},
            center = {Fraction2d 0.2, 0.2},
            radius = 1.0
        }
    set self._inner-down-right-gfp =
        {RadialGradientFillPattern
            {Spectrum.from-endpoints "white", bc},
            center = {Fraction2d 0.3, 0.3},
            radius = 1.0
        }
  }
  {method private {setup-label}:void
    let btn = self.control asa CommandButton
    {if-non-null label=btn.label then
        set self._ellipse =
            {EllipseGraphic
                horigin="center",
                vorigin="center",
                color = self._inner-up-right-gfp
            }
        set self._label-box =
            {OverlayBox
                self._ellipse,
                {HBox
                    {Fill width=15pt},
                    {VBox
                        {Fill height= 15pt},
                        {TextFlowBox
                            horigin="center",
                            vorigin="center",
                            label
                        },
                        {Fill height= 15pt}
                    },
                    {Fill width=15pt}
                }
            }
    }
    {self.add-internal self._label-box, replace?=true}
  }
}

{let ellipse-laf:StandardLookAndFeel =
    {StandardLookAndFeel}
}
||Registers EllipticalCommandButtonUI as the UI for command buttons
{let registered:bool =
    {ellipse-laf.register-ui
        CommandButton,
        EllipticalCommandButtonUI}
}
{set ellipse-laf.color="green"}
{set ellipse-laf.control-color="blue"}
{Dialog
    || The following line sets look and feel for this dialog and
    || its children to one that makes command buttons elliptical
    || rather than the default
    ||    look-and-feel = ellipse-laf,
    {spaced-vbox
        background="wheat",
        || CommandButton 1
        {CommandButton
            || The following line sets the UI object for this button
            || to the elliptical UI defined above
            ui-object = {EllipticalCommandButtonUI},
            control-color = {FillPattern.get-red},
            label={bold Uses ui-object}
        },
        || CommandButton 2
        {CommandButton
            || The following line sets look and feel for this button to one that
            || makes command buttons elliptical rather than the default
            look-and-feel = ellipse-laf,
            label={bold Uses look-and-feel}
        },
        || CommandButton 3
        {CommandButton
            enabled?=false,
            label={bold disabled}
        }
    }
}
|| The following line resets the default look and feel
|| {set the-default-look-and-feel.target-look-and-feel = ellipse-laf}

Click to view Kamal's profile Curl Kamal 149 posts since
Oct 17, 2007
2. Re: Need more details on Standard<Control>UI May 23, 2008 6:16 AM
There could be various reasons why you will want to subclass a UI, One of the common use case is to rearrange the various parts of a control UI the way you want it.

The example below (serach for "StandardScrollbarUI.create-main-panel" in Curl help), the buttons to increment and decrement the Scrollbar are rearranged to be both at one end.

All the controls in Curl are open sourced. If you have downloaded the Curl IDE then they are already in your m/c. On a win32 m/c, by default it will be in:

C:\Program Files\Curl Corporation\Surge\7\ide\gui\controls.

Note that you may have multiple Curl (major ) versions installed and so you may have multiple directories (like 4, 5, 6) under the Surge directory above.

Some of the controls use other controls in their UIs, like the Scrollbar, Slider, CommandButton, DateField etc and some don't, like the TextField, CommandButton etc.

{curl 6.0 applet}
 
|| A custom Scrollbar UI.
{define-class package CustomUI {inherits StandardScrollbarUI}
  {constructor package {default
                           control:#Scrollbar = null,
                           ...
                       }
    {construct-super.StandardScrollbarUI
        control = control, 
        ...
    }
  }
 
  {getter private {vertical-bar?}:bool
    {return self.scrollbar.direction == Orientation.vertical}
  }
 
  {method protected open {create-main-panel}:Graphic
    {return
        {if self.vertical-bar? then
            {VBox
                vorigin = "top",
                halign = "left",
                hstretch? = true,
                framelike-stretch? = true,
                control-appearance-changeable? = true,
                {self.create-channel},
                {self.create-decrease-button},
                {self.create-increase-button}
            }
         else
            {HBox
                horigin = "left",
                valign = "top",
                vstretch? = true,
                framelike-stretch? = true,
                control-appearance-changeable? = true,
                {self.create-channel},
                {self.create-decrease-button},
                {self.create-increase-button}
            }
        }
    }
  }
 
  {method public open {draw-channel
                          renderer2d:Renderer2d,
                          bounds:GRect
                      }:void
    let constant psize:Distance = renderer2d.pixel-size
    let constant psize2:Distance = psize + psize
    let constant psize4:Distance = psize2 + psize2
    
    {if self.vertical-bar? then
        {renderer2d.render-rectangle
            -bounds.lextent + (bounds.width / 2) - psize2,
            -bounds.ascent,
            psize4,
            bounds.height,
            fill-pattern = FillPattern.gray
        }
     else
        {renderer2d.render-rectangle
            -bounds.lextent,
            -bounds.ascent +  (bounds.height / 2) - psize2,
            bounds.width,
            psize4,
            fill-pattern = FillPattern.gray
        }
    }
  }
}
 
{Scrollbar ui-object = {CustomUI}, direction = "horizontal"}