How to create a standalone app

Creating a standalone app based on Glamorous Toolkit is as simple as opening an application specific window and closing the existing GT window. This can be achieved in just a few steps.

BlSpace Object << #BlSpace traits: {TBlEventTarget + TBlSpaceProperties + TBlDebug}; slots: { #id . #host . #hostSpace . #extent . #position . #root . #resizable . #borderless . #dirtyAreas . #eventDispatcher . #eventListener . #eventRecorder . #mouseProcessor . #focusProcessor . #keyboardProcessor . #focusChain . #dragboard . #nextPulseRequested . #currentCursor . #session . #focused . #title . #fullscreen . #fullsize . #layoutError . #tasks . #time . #touchProcessor . #frame . #gestureProcessor . #elementsNeedingPaint . #elementsNeedingLayout . #telemetry . #reference . #elementsNeedingStyle . #elementsNeedingPropertiesComputation . #iconStencil }; sharedVariables: { #UniqueIdGenerator . #UserFontScaleFactor . #UserScaleFactor }; tag: 'Space'; package: 'Bloc' is responsible for displaying a scene in a separate window. Users can customise window's title or extent. To spawn a window send BlSpace>>#show show "Open me in a window and show it to the user" "delegate showing work to the Universe" (BlParallelUniverse forHost: self host class) openSpace: self to an instance of the space.

aSpace := BlSpace new
		addChild: GtCreateStandaloneAppHowToGuide new helloWorldScene;
		extent: 800@600;
		title: 'Hello World'.
aSpace show
  

By default, a space offers the possibility to close the window, but if all windows are closed, the image will still be running. If you want to associate the closing of the window with stopping the image, you can listen to the BlSpaceClosedEvent BlSpaceEvent << #BlSpaceClosedEvent slots: { #space }; tag: 'Events-Type-Space'; package: 'Bloc' and exit when it happens:

aSpace
	when: BlSpaceClosedEvent
	do: [ Smalltalk snapshot: false andQuit: true ].
  

The Glamorous Toolkit comes with a GtWorld BlSpace << #GtWorld slots: { #worldElement }; tag: 'UI'; package: 'GToolkit-World' opened by default. When creating a standalone app based on GT we should close that window, which can be done in two steps.

In order to shutdown the process when a window is closed, we add a BlSpaceShutdownOnCloseListener BlBasicEventHandler << #BlSpaceShutdownOnCloseListener slots: { #shouldSave }; tag: 'Space - Events'; package: 'Bloc' as an event handler to a BlSpace Object << #BlSpace traits: {TBlEventTarget + TBlSpaceProperties + TBlDebug}; slots: { #id . #host . #hostSpace . #extent . #position . #root . #resizable . #borderless . #dirtyAreas . #eventDispatcher . #eventListener . #eventRecorder . #mouseProcessor . #focusProcessor . #keyboardProcessor . #focusChain . #dragboard . #nextPulseRequested . #currentCursor . #session . #focused . #title . #fullscreen . #fullsize . #layoutError . #tasks . #time . #touchProcessor . #frame . #gestureProcessor . #elementsNeedingPaint . #elementsNeedingLayout . #telemetry . #reference . #elementsNeedingStyle . #elementsNeedingPropertiesComputation . #iconStencil }; sharedVariables: { #UniqueIdGenerator . #UserFontScaleFactor . #UserScaleFactor }; tag: 'Space'; package: 'Bloc' . It should be removed before we close such spaces.

GtWorld allInstances do: [ :eachWorld | eachWorld removeShutdownListener ]
  

To close an opened window, it is enough to just send BlSpace>>#close close "Delegate closing work to the Universe" (BlParallelUniverse forHost: self host class) closeSpace: self to an intended space.

GtWorld allInstances do: [ :eachWorld | eachWorld close ]
  

By design, GT checks for closed GtWorld BlSpace << #GtWorld slots: { #worldElement }; tag: 'UI'; package: 'GToolkit-World' instances. And if the snapshotted image doesn't have any opened windows, then GT will spawn the Morphic World by attaching a renderer and a window to it.

The class hierarhy diagram for those renderers is described below.

view := GtMondrian new.
view nodes
	umlClassShapeWithName: [ :each | each name ];
	with: AbstractWorldRenderer withAllSubclasses.
view edges
	fromCenterBottom;
	toCenterTop;
	connectFrom: #superclass.
view layout tree levelDistance: 100.
view
  

The Image is calling the next method to decide which renderer it will use, it is performing a search based on a class priority priority ^ 0 and an isApplicableFor: isApplicableFor: aWorld ^ self subclassResponsibility properties. AbstractWorldRenderer>>#detectCorrectOneForWorld: detectCorrectOneForWorld: aWorld | aRenderer | MainWorldRenderer ifNotNil: [ ^ MainWorldRenderer ]. (self allSubclasses sorted: [ :a :b | a priority > b priority ]) detect: [ :aClass | aClass isApplicableFor: aWorld ] ifFound: [ :aClass | aRenderer := aClass forWorld: aWorld ]. ^ MainWorldRenderer := aRenderer

Thus, you need to create you own subclass to override these properties, and you need to make sure, what your's class priority is greater than in others.

Evaluate the next snippet to create you own minimal subclass of the GtNullWorldMorphicRenderer NullWorldRenderer << #GtNullWorldMorphicRenderer slots: {}; sharedVariables: { #isEnabled }; tag: 'UI'; package: 'GToolkit-World'

GtNullWorldMorphicRenderer subclass: #MyWorldRenderer.
MyWorldRenderer class 
	compile: 'priority ^ 200';
	compile: 'isApplicableFor: aWorld ^true'. 
  

Once the intended application window is opened and there are no more GT windows we can save the image (without quitting):

Smalltalk snapshot: true andQuit: false.