ElectronWorkstation.st
author Stefan Vogel <sv@exept.de>
Tue, 28 Apr 2020 15:27:50 +0200
changeset 9037 75c0cc5c9955
parent 8983 95db637a39e0
permissions -rw-r--r--
#BUGFIX by stefan class: FontDescription changed: #fromLiteralArrayEncoding: set sizeUnit

"{ Encoding: utf8 }"

"{ Package: 'stx:libview' }"

"{ NameSpace: Smalltalk }"

DeviceWorkstation subclass:#ElectronWorkstation
	instanceVariableNames:'bridge rendererBridgePerBrowserWindow eventQueue'
	classVariableNames:'ExposureMask StructureNotifyMask KeyPressMask KeyReleaseMask
		PointerMotionMask EnterWindowMask LeaveWindowMask ButtonPressMask
		ButtonMotionMask ButtonReleaseMask PropertyChangeMask'
	poolDictionaries:''
	category:'Interface-Graphics'
!

DeviceGraphicsContext subclass:#ElectronGraphicsContext
	instanceVariableNames:''
	classVariableNames:''
	poolDictionaries:''
	privateIn:ElectronWorkstation
!

!ElectronWorkstation class methodsFor:'documentation'!

documentation
"
    experimental work in progress: a Display which draws in an electron application.
"
!

example1
    |dpy result|

    Smalltalk at:#Display2 put:(dpy := ElectronWorkstation newFor:'localhost:8888').
    result := dpy bridge 
                callFunction:'helloE'
                withArguments:#().
    Transcript showCR:result.
    self assert:result == 123.
    dpy close.

    "
     Display2 close
    "
!

example2
    |dpy result|

    Smalltalk at:#Display2 put:(dpy := ElectronWorkstation newFor:'localhost:8888').
    result := dpy bridge 
                eval:'return 10+20'
                withArguments:#().
    Transcript showCR:result.
    self assert:result == 30.

    result := dpy bridge 
                eval:'return arguments[0]+arguments[1]+1'
                withArguments:#(10 20).
    Transcript showCR:result.
    self assert:result == 31.

    dpy close.

    "
     Display2 close
    "
!

example3a
    |v1 dpy|

    Smalltalk at:#Display2 put:(dpy := ElectronWorkstation newFor:'localhost:8888').

    v1 := (TopView onDevice:dpy)
            extent:(200 @ 150);
            label:'Hello1';
            open.

    Delay waitForSeconds:3.
    v1 close.

    Delay waitForSeconds:1.
    dpy close.

    "
     Display2 close
    "
!

example3b
    |v1 v2 dpy|

    Smalltalk at:#Display2 put:(dpy := ElectronWorkstation newFor:'localhost:8888').

    v1 := (TopView onDevice:dpy)
            extent:(200 @ 150);
            label:'Hello1';
            open.
    v2 := (TopView onDevice:dpy)
            extent:(200 @ 150);
            label:'Hello2';
            open.

    Delay waitForSeconds:3.
    v1 close.
    Delay waitForSeconds:3.
    v2 close.
    Delay waitForSeconds:1.
    dpy close.

    "
     Display2 close
    "
!

example4
    |v1 v2 dpy|

    Smalltalk at:#Display2 put:(dpy := ElectronWorkstation newFor:'localhost:8888').

    v1 := (TopView onDevice:dpy)
            extent:(200 @ 150);
            label:'Hello1';
            open.
    v2 := (TopView onDevice:dpy)
            extent:(200 @ 150);
            label:'Hello2';
            open.

    Delay waitForSeconds:1.
    v1 unrealize.
    Delay waitForSeconds:1.
    v1 realize.
    Delay waitForSeconds:1.
    v1 close.

    Delay waitForSeconds:1.
    v2 unrealize.
    Delay waitForSeconds:1.
    v2 realize.
    Delay waitForSeconds:1.
    v2 close.

    Delay waitForSeconds:1.
    dpy close.

    "
     Display2 close
    "
!

example5
    |v1 dpy|

    Smalltalk at:#Display2 put:(dpy := ElectronWorkstation newFor:'localhost:8888').

    v1 := (TopView onDevice:dpy)
            extent:(200 @ 150);
            label:'Hello1';
            open.

    Delay waitForSeconds:1.
    v1 extent:(400@300).
    Delay waitForSeconds:1.
    v1 extent:(200@150).
    Delay waitForSeconds:1.
    v1 close.

    Delay waitForSeconds:1.
    dpy close.

    "
     Display2 close
    "
!

example5b
    |v1 dpy|

    Smalltalk at:#Display2 put:(dpy := ElectronWorkstation newFor:'localhost:8888').

    v1 := (TopView onDevice:dpy)
            extent:(200 @ 150);
            label:'Hello1';
            open.

    Delay waitForSeconds:1.
    v1 origin:(0@0).
    Delay waitForSeconds:1.
    v1 origin:(200@200).
    Delay waitForSeconds:1.
    v1 close.

    Delay waitForSeconds:1.
    dpy close.

    "
     Display2 close
    "
! !

!ElectronWorkstation class methodsFor:'class initialization'!

initialize
    ExposureMask        := 1 bitShift:0.
    StructureNotifyMask := 1 bitShift:1.
    KeyPressMask        := 1 bitShift:2.
    KeyReleaseMask      := 1 bitShift:3.
    PointerMotionMask   := 1 bitShift:4.
    EnterWindowMask     := 1 bitShift:5.
    LeaveWindowMask     := 1 bitShift:6.
    ButtonPressMask     := 1 bitShift:7.
    ButtonMotionMask    := 1 bitShift:8.
    ButtonReleaseMask   := 1 bitShift:9.
    PropertyChangeMask  := 1 bitShift:10.
! !

!ElectronWorkstation class methodsFor:'logging'!

logFacility
    "the 'log facility';
     this is used by the Logger both as a prefix to the log message, 
     and maybe (later) used to filter and/or control per-facility log thresholds."

    ^ self nameWithoutPrefix
! !

!ElectronWorkstation methodsFor:'accessing'!

bridge
    ^ bridge
!

rendererBridge
    ^ rendererBridge
! !

!ElectronWorkstation methodsFor:'accessing & queries'!

defaultEventMask
    "return a mask to enable some events by default."

    ^ ExposureMask | StructureNotifyMask |
      KeyPressMask | KeyReleaseMask |
      PointerMotionMask |
      EnterWindowMask | LeaveWindowMask |
      ButtonPressMask | ButtonMotionMask | ButtonReleaseMask |
      PropertyChangeMask

!

isOpen
    "return true, if there is a valid connection to the display"

    ^ bridge notNil and:[bridge isConnected]
!

supportsPNGImages
    "return true, if this device directly supports png images.
     Currently none does (except browser windows)"    

    ^ true
! !

!ElectronWorkstation methodsFor:'event handling'!

eventMaskFor:anEventSymbol
    ^ 1
!

eventPending 
    ^ eventQueue notNil and:[eventQueue notEmpty]

!

setEventMask:eventMask in:drawableId
    ^ self
! !

!ElectronWorkstation methodsFor:'font stuff'!

ascentOf:aFontId
    "/ the font name is its ID
    Logger info:'unimplemented: ascentOf'.
    ^ 17
!

descentOf:aFontId
    "/ the font name is its ID
    Logger info:'unimplemented: descentOf'.
    ^ 17
!

getFontWithFamily:familyString face:faceString style:styleString size:sizeArg sizeUnit:sizeUnit encoding:encodingSym
    "try to get the specified font, return id.
     If not available, try next smaller font.
     If no font fits, return nil"

    "/ return the font name as ID
    ^ '%1-%2-%3-%4-%5-%6' 
        bindWith:familyString 
        with:faceString 
        with:styleString 
        with:sizeArg 
        with:sizeUnit 
        with:encodingSym
!

maxAscentOf:aFontId
    "/ the font name is its ID
    Logger info:'unimplemented: maxAscentOf'.
    ^ 17
!

maxDescentOf:aFontId
    "/ the font name is its ID
    Logger info:'unimplemented: maxDescentOf'.
    ^ 17
!

maxWidthOfFont:aFontId
    "/ the font name is its ID
    Logger info:'unimplemented: maxWidthOfFont'.
    ^ 20
!

minWidthOfFont:aFontId
    "/ the font name is its ID
    Logger info:'unimplemented: minWidthOfFont'.
    ^ 20
!

widthOf:aString from:index1 to:index2 inFont:aFontId
    "/ the font name is its ID
    Logger info:'unimplemented: widthOf'.
    ^ (index2-index1+1) * 20
! !

!ElectronWorkstation methodsFor:'initialization & release'!

closeConnection
    "close down connection to Display - usually never done"

    |b|

    (b := bridge) notNil ifTrue:[
        bridge := nil.
        b close
    ].

    "Created: 13.1.1997 / 22:10:07 / cg"
!

connectToRenderer:port
    |rendererBridge|

    [
        rendererBridge := bridge class newBridgeForHost:(bridge host) port:port.
        rendererBridge connectWithTimeoutMS:5000.
    ] ifCurtailed:[
        rendererBridge notNil ifTrue:[ rendererBridge close ].
        rendererBridge := nil.
    ].
!

initializeFor:aHostAndPortStringOrNil
    |nodeBridge _ElectronBridge|

    nodeBridge := Smalltalk requirePackage:'exept:bridgeFramework/nodeJSBridge'.
    _ElectronBridge := nodeBridge classNamed:'NodeJSBridge::ElectronBridge'.
    bridge := _ElectronBridge newBridgeForHostAndPort:aHostAndPortStringOrNil.
    [
        bridge startBridgeIn:'.' debug:true.
        bridge connectWithTimeoutMS:5000.
    ] ifCurtailed:[
        bridge notNil ifTrue:[ bridge close ].
        bridge := nil.
    ].

    "/ every browser window has its own renderer!!
    rendererBridgePerBrowserWindow := Dictionary new.

    self initializeScreenProperties.
    self initializeDefaultValues.
    self initializeKeyboardMap.
    self initializeDeviceSignals.
    self initializeDeviceResourceTables.

    Screen default isNil ifTrue:[
        "not initialized yet?"
        self initializeViewStyle.
    ].

    "
     ElectronWorkstation newFor:'localhost:8098'
    "
!

initializeScreenProperties
    super initializeScreenProperties.

    monitorType := #unknown.
    visualType := #TrueColor.
    depth := 24.
    redShift := 16.
    greenShift := 8.
    blueShift := 0.

    Display notNil ifTrue:[
        width := Display width.
        height := Display height.
    ] ifFalse:[
        width := 1280.
        height := 1024.
    ].
! !

!ElectronWorkstation methodsFor:'keyboard stuff'!

ungrabKeyboard
    Logger info:'unimplemented: ungrabKeyboard'.
! !

!ElectronWorkstation methodsFor:'pointer stuff'!

pointerPosition
    Logger info:'unimplemented: pointerPosition'.
    ^ 0@0
!

ungrabPointer
    Logger info:'unimplemented: ungrabPointer'.
! !

!ElectronWorkstation methodsFor:'private'!

jsonEncode:aReferenceOrObject
    aReferenceOrObject isBridgeProxy ifFalse:[^ aReferenceOrObject].
    ^ Dictionary new
        at:'__ref__' put:aReferenceOrObject bridgeRefId;
        yourself.
! !

!ElectronWorkstation methodsFor:'window stuff'!

createWindowFor:aView type:typeSymbol origin:org extent:ext
        minExtent:minE maxExtent:maxE borderWidth:bw subViewOf:superViewOrNil
        style:styleSymbol inputOnly:inp
        label:label owner:owner
        icon:icn iconMask:icnM iconView:icnV

    "returns a remote window handle"

    |result args|

    "/ does not work - BrowserWindow is invisible 
"/    result := bridge 
"/                eval:'var win = new BrowserWindow(
"/                                    {
"/                                     width: arguments[0], 
"/                                     height: arguments[1]
"/                                    });
"/                      return makeRef(win, "electronWindow");'
"/                withArguments:{ ext x . ext y }.
    args := OrderedDictionary new
            at:'width' put:ext x;
            at:'height' put:ext y;
            at:'x' put:org x;
            at:'y' put:org y;
            yourself.
    superViewOrNil notNil ifTrue:[
        args at:'parent' put:(self jsonEncode:superViewOrNil id)
    ].
    result := bridge callFunction:'createWindow' withArguments:{ args }.
    ^ result
!

destroyView:aView withId:aWindowHandle
    "/ close is sent to the bridgeObject
    aWindowHandle close.
    "/ bridge 
    "/     invokeMethod:'close' 
    "/     object:aWindowHandle 
    "/     arguments:#()
!

mapWindow:aWindowId
    "map a window"

    "/ show is sent to the bridgeObject
    aWindowId show.
!

moveWindow:aWindowHandle x:x y:y
    "resize a window"

    bridge 
        callFunction:'win_setOrigin'
        withArguments:{ aWindowHandle   .
                    (OrderedDictionary new
                        at:'x' put:x;
                        at:'y' put:y;
                        yourself)
                   }
!

resizeWindow:aWindowHandle width:w height:h
    "resize a window"

    bridge 
        callFunction:'win_setExtent'
        withArguments:{ aWindowHandle   .
                    (OrderedDictionary new
                        at:'width' put:w;
                        at:'height' put:h;
                        yourself)
                   }
!

setWindowBackground:aColorIndex in:aWindowHandle
    "set a windows background color"

    "/ aWindowHandle getBackgroundColor
    aWindowHandle setBackgroundColor:(aColorIndex printfPrintString:'#%06x')
    "/ Logger info:'unimplemented: setWindowBackground:'.
!

setWindowName:aString in:aWindowHandle
    "define a windows name (i.e. windowTitle)"

    aWindowHandle setTitle:aString.
    "/ Logger info:'unimplemented: setWindowName:'.
!

testResize:windowHandle
    bridge callFunction:'testResize' withArguments:{ windowHandle }.
!

unmapWindow:aWindowId
    "unmap a window"

    "/ hide is sent to the bridgeObject
    aWindowId hide.
! !

!ElectronWorkstation::ElectronGraphicsContext class methodsFor:'documentation'!

documentation
"
    documentation to be added.

    class:
        <a short class summary here, describing what instances represent>

    responsibilities:    
        <describing what my main role is>

    collaborators:    
        <describing with whom and how I talk to>

    API:
        <public api and main messages>
        
    example:
        <a one-line examples on how to use - can also be in a separate example method>

    implementation:
        <implementation points>

    [author:]
        exept MBP

    [instance variables:]

    [class variables:]

    [see also:]

"
! !

!ElectronWorkstation class methodsFor:'documentation'!

version_CVS
    ^ '$Header$'
! !


ElectronWorkstation initialize!