Example:
initialize
super initialize.
self color: Color orange.
self extent: 200@200.
self borderWidth: 2.
self borderColor: Color black.
m1 openInWorld. "or World addMorph: m1" m1 addMorph: m2. m1 addMorphFront: m3. m1 addMorphBack: m4. m4 owner submorphs do: [:m | m delete].Adding a morph to another removes the morph from its former owner.
World is a global variable that refers to the world.
Several messages that both reads and sets coordinates.
Classes Point and Rectangle for geometry.
Examples:
m2 position: m1 position. m2 center: World center. m2 left: m1: right. m2 extent: 200@200. m2 bounds: ((0@0) extent: (200@200)). m2 bounds: World bounds.More messages:
left (Integer) right (Integer) top (Integer) bottom (Integer) position (Point) center (Point) topLeft (Point) bottomRight (Point) extent (Point) bounds (Rectangle)
Example:
drawOn: aCanvas
aCanvas fillOval: self bounds color: self color.
This draws "off screen", Squeak handles double-buffring.
The message changed can be sent to a morph to cause it to redisplay. Many methods in class Morph calls changed, there is seldom a need to use this method.
The display can be drawn on directly using the global variable Display, which is an instance of class Form.
Form is the basic class for images.
BitBlt and WarpBlt are classes for copying images. (More about this in a forthcoming lecture).
Mouse down/up methods:
handlesMouseDown: event
^true
mouseDown: event
event
self left: self left + 10.
mouseUp: event
self left: self left - 10.
Mouse drag metods:
handlesMouseOver: event
^ true
mouseMove: event
self center: event cursorPoint.
mouseEnter: anEvent
self color: Color red.
mouseLeave: anEvent
self color: Color blue.
halo
on: #mouseDown
send: #delete
to: self.
halo
on: #mouseDown
send: #openPopupMenu
to: self.
delete is an existing methods, openPopupMenu needs to be implemented.
openPopupMenu
| menu |
menu := MenuMorph new.
menu addTitle: 'Main menu'.
menu add: 'New Example Morph' target: self action: #createExampleMorph.
menu add: 'New Watch Morph' target: self action: #createWatchMorph.
menu addLine.
menu add: 'Undo' target: self action: #undoLastCommand.
menu addLine.
menu add: 'Clear' target: self action: #clearDrawingArea.
menu popUpInWorld.
createExampleMorph
| m |
m := ExampleMorph new.
m center: World primaryHand position.
World primaryHand addMorph: m.
createWatchMorph
| m |
m := WatchMorph new.
m center: World primaryHand position.
World primaryHand addMorph: m.
undoLastCommand
...
clearDrawingArea
self sumorphs do: [:m | m delete].
"There is a method for this in class Morph: removeAllMorphs"
button := SimpleButtonMorph new. button borderWidth: 2. button extent: 100@35. button label: 'Delete me'. button target: button. button actionSelector: #delete.
Delete halos (e.g. use removeAllMorphs) in mouseLeave.
Code the desired behaviour in the halos, e.g. as "listener" methods in the main morph (the morph having the halos).
Morph subclass: #DemoMorph
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
category: 'ETE257-Demo'
initialize
super initialize.
self setAppearence.
self createMouseOverHandlers.
setAppearence
self color: Color orange.
self extent: 200@200.
self borderWidth: 2.
self borderColor: Color black.
createMouseOverHandlers
self on: #mouseEnter send: #createHalos to: self.
self on: #mouseLeave send: #deleteHalos to: self.
createHalos
| deleteHalo menuHalo |
deleteHalo := EllipseMorph new.
menuHalo := EllipseMorph new.
deleteHalo
color: Color red;
borderWidth: 1;
borderColor: Color black;
extent: 20@20.
menuHalo
color: Color blue;
borderWidth: 1;
borderColor: Color black;
extent: 20@20.
deleteHalo on: #mouseDown send: #deleteMe to: self.
menuHalo on: #mouseDown send: #popupMenu to: self.
deleteHalo topLeft: self topLeft + 5.
menuHalo topLeft: deleteHalo topRight + (10@0).
self addMorph: deleteHalo.
self addMorph: menuHalo.
deleteHalos
self submorphs do: [:m | m delete].
drawOn: aCanvas
"Draw the morph here."
popupMenu
"Popup a menu here"
deleteMe
self delete.
EllipseMorph subclass: #ResizeHalo
instanceVariableNames: 'offset'
classVariableNames: ''
poolDictionaries: ''
category: 'ETE257-Demo'
initialize
super initialize.
self
color: Color yellow;
borderWidth: 1;
borderColor: Color black;
extent: 20@20.
handlesMouseDown: event
^true
mouseDown: event
offset := event cursorPoint - self owner bottomRight.
mouseMove: event
self owner extent: (event cursorPoint - self owner topLeft - offset).
self bottomRight: self owner bottomRight - 3.
When using this technique (the Command design pattern) one uses one object for each command.
A command object has two methdods, do and undo, and stores the information needed to undo the command.
Example of a command that deletes a morph:
Object subclass: #DeleteCommand
instanceVariableNames: 'morph parent'
classVariableNames: ''
poolDictionaries: ''
category: 'ETE257-Test'
do
morph delete.
"The following is an example of how
the command object is added to a list
of commands kept by an undo manager.
Note that you have to write the UndoManger
class yourself."
UndoManager addCommand: self.
morph: aMorph
morph := aMorph.
parent := aMorph owner.
undo
parent addMorph: morph.
Example of how to use the command object in a menu:
command := DeleteCommand new. command morph: self. menu add: 'Delete' target: command action: #do.To undo the command, send the undo message to it.
Typically, command objects are kept in a list, and and the undo mechanism uses this list when undoing commands.
There is some built-in support in Morphic for command objects and undo in the classes Command and Command History. (Note that the above examples does not use this mechanism.)
layoutChanged
super layoutChanged. "I sthis needed?"
"Position submorphs here"
Table layout, set up the layout in for example initialize:
initialize
super initialize.
self
color: Color white;
borderWidth: 0;
changeTableLayout;
listDirection: #leftToRight;
cellPositioning: #topLeft.
menuPanel := Morph new.
menuPanel
color: Color orange muchLigther;
borderWidth: 0;
hResizing: #shrinkWrap;
vResizing: #spaceFill;
changeTableLayout;
listDirection: #topToBottom;
cellPositioning: #topLeft;
cellInset: 10@10;
layoutInset: 5@5.
"ToDo: Add some objects (e.g. buttons) to the menu."
drawingArea := Morph new.
drawingArea
color: Color white;
borderWidth: 0.
self addMorphBack: menuPanel.
self addMorphBack: drawingArea.
self extent: 600@500.
The symbol #rigid can also be used, they you set
the width or height yourself.
You can also create a table layout with:
self layoutPolicy: TableLayout new.
(Instead of using self changeTableLayout.)
Proportional layout:
initialize
| frame |
super initialize.
self layoutPolicy: ProportionalLayout new.
frame _ LayoutFrame
fractions: (0@0 corner: 0.0@1.0)
offsets: (0@0 corner: 100@0).
self addMorph: menuPanel fullFrame: frame.
frame _ LayoutFrame
fractions: (0@0 corner: 1.0@1.0)
offsets: (100@0 corner: 0@0).
self addMorph: drawingArea fullFrame: frame.
Write your own layout manager:
LayoutPolicy subclass: #MyLayout
layout: aMorph in: newBounds
aMorph doLayout.
In the above example the morphs handles its own
layout in the method doLayout (you have to write this
method yourself). This is a more stable approach
than overriding layoutChanged.
WatchMorph new openInWindow. WatchMorph new openInWindowLabeled: 'Clock'. window := WatchMorph new openInWindowLabeled: 'Clock'. window color: Color green.
mouseDown: event
event hand grabMorph: self.
"Or: event hand addMorph: self"
You can use World primaryHand if you do not have an event object.
Call enableDragNDrop on your own morph to allow drag&drop.
The following are some of the methods you can override to control drag/drop actions:
aboutToBeGrabbedBy: aHand justDroppedInto: aMorph event: anEvent justGrabbedFrom: formerOwner wantsDroppedMorph: aMorph event: evt wantsToBeDroppedInto: aMorph
answer := FillInTheBlankMorph
request: 'What is your name?'
initialAnswer: ''.
answer ifNotEmpty: [
PopUpMenu inform: 'Welcome ', answer, '!'.
].
Examples:
"Open a picture file." f := Form fromFileNamed: 'Blobb.gif'. "It is usually good to use 32 bit forms." f := f asFormOfDepth: 32. "Scale a form." f := f scaledIntoFormOfSize: 50@50. "Draw a form on a canvas (with transparency)." canvas paintImage: form at: 100@100. "Draw a form directly on the display." f displayOn: Display at: 100@100. "Draw with a combination rule." f displayOn: Display at: 200@300 rule: Form reverse. "Save part of the display as an PNG image." form := Form fromUser. writer := PNGReadWriter on: (FileStream forceNewFileNamed: 'MyPicture.png') binary. writer nextPutImage: form. writer close. "Save a morph as a GIF image" clock := WatchMorph new. GIFReadWriter putForm: clock imageForm onFileNamed: 'Clock.gif'.
Simple example of using WarpBlt to scale a form:
f := Form fromUser.
warp := (WarpBlt toForm: Display)
cellSize: 1;
sourceForm: f;
cellSize: 2; "installs a colormap"
combinationRule: Form over.
r := (0@0) extent: f extent.
pts := {r topLeft. r bottomLeft. r bottomRight. r topRight}.
warp
copyQuad: pts
toRect: ((0@0) extent: (200@200)).
Note that it is usually easier to use high-level methods
in Form to manipulate images, and to use drawing
methods in Canvas and FormCanvas to draw images in
more fancy ways, than to use BitBlt and WarpBlt.
Here is the way to use ReferenceStream:
rr := ReferenceStream fileNamed: 'test.obj'. rr nextPut: obj. rr close.To get it back:
rr := ReferenceStream fileNamed: 'test.obj'. obj := rr next. rr close.You can also use SmartRefStream, which can handle changes in the code for the objects.
result :=
(
StandardFileMenu
oldFileMenu: (FileDirectory default)
withPattern: '*.*'
)
startUpWithCaption: 'Select a picture file'.
result ifNotNil: [
picture := Form fromBinaryStream:
(result directory readOnlyFileNamed: result name) binary.
sketch := SketchMorph new.
sketch form: picture.
sketch openInHand.
].
SketchMorph is a morph that can be used to display forms. (You can
also make your own morph class for this purpose.)
PopUpMenu inform: 'System is going down!', String cr, 'Take cover!'.