Implementing the Components-Pattern in monkey-x

Monkey Forums/Monkey Beginners/Implementing the Components-Pattern in monkey-x

Hero(Posted 2015) [#1]
Hi guys,

I am looking to implement the components programming pattern in monkey-x, in order to decouple the different domains (graphics, controls, audio) from each other while retaining a scene-graph architecture.

So far, my implementation looks like this:

Strict

Import mojo2

Interface IDrawable
	
	Method Surface:Canvas() Property

	Method Render:Void()
	
End


Strict

Import interfaces.idrawable

Interface IGraphicsComponent
	
	Method Render:Void(drawable:IDrawable)
	
End


Strict

Import mojo2
Import interfaces.idrawable
Import interfaces.igraphicscomponent

Class cPlayfield Implements IDrawable

	Private
		Field surface:Canvas
		Field graphicsComponent:IGraphicsComponent = New cPlayfieldGraphicsComponent()
		
	Public
		Field Width:Int
		Field Height:Int
		
		Method Surface:Canvas() Property
			Return surface
		End
		
	Method New(_surface:Canvas, _width:Int, _height:Int)
		surface = _surface
		Width = _width
		Height = _height
	End
	
	Method Render:Void()
		graphicsComponent.Render(Self)
	End
	
End

Class cPlayfieldGraphicsComponent Implements IGraphicsComponent

	Method Render:Void(drawable:IDrawable)

		Local playfield:cPlayfield = cPlayfield(drawable)
	
		playfield.Surface.Clear(0, 0, 0)
		playfield.Surface.SetColor(0, 0, 1)

		playfield.Surface.DrawRect(0, 0, playfield.Width, playfield.Height)
		
		playfield.Surface.SetColor(1, 1, 1)
		playfield.Surface.DrawText("Hello World.", 10, 10)
					
	End

End


Do you think this is a valid approach to the component pattern in monkey-x?


Hero(Posted 2015) [#2]
This is propably a better example:

Strict

Import interfaces.idrawable
Import interfaces.icontrollable
Import interfaces.igraphicscomponent
Import interfaces.iinputcomponent
Import config.settings

Class cSlider Implements IDrawable, IControllable

	Private
		Field surface:Canvas
		Field graphicsComponent:IGraphicsComponent = New cSliderGraphicsComponent()
		Field inputComponent:IInputComponent = New cSliderInputComponent()
	
	Public
		Field PosX:Float
		Field PosY:Float
		Field Width:Float
		Field Radius:Float
		Field Value:Float
		Field MinValue:Float
		Field MaxValue:Float
		Field IsDown:Bool
		
		Method Surface:Canvas() Property
			Return surface
		End
		
	' Constructor
	Method New(_surface:Canvas, _posX:Float, _posY:Float, _width:Float, _radius:Float = 10, _minValue:Float = 0, _maxValue:Float = 1, _value:Float = 0)
		surface = _surface
		PosX = _posX
		PosY = _posY
		Width = _width
		Radius = _radius
		MinValue = _minValue
		MaxValue = _maxValue
		Value = _value
	End
	
	' Render
	Method Render:Void()
		graphicsComponent.Render(Self)
	End
	
	' Handle Input
	Method HandleInput:Void()
		inputComponent.HandleInput(Self)
	End
	
End

Class cSliderGraphicsComponent Implements IGraphicsComponent

	Method Render:Void(drawable:IDrawable)

		Local slider:cSlider = cSlider(drawable)
		Local position:Float = slider.PosX + (slider.Value * (slider.Width / slider.MaxValue))
		Local radius:Float = slider.Radius
		
		' Draw Line
		slider.Surface.SetAlpha(1)
		slider.Surface.SetColor(1, 1, 1)
		slider.Surface.DrawLine(slider.PosX, slider.PosY, slider.PosX + slider.Width, slider.PosY)
		
		If slider.IsDown Then slider.Surface.SetColor(1, 0, 0)
					
		' Draw Handle
		slider.Surface.DrawCircle(position, slider.PosY, radius)
	End
End

Class cSliderInputComponent Implements IInputComponent

	Private
		Field settings:cSettings = cSettings.Instance()
	
	Public
	
	Method HandleInput:Void(controllable:IControllable)
		Local slider:cSlider = cSlider(controllable)
		
		Local actualPosX:Float = settings.GridOffset_X + slider.PosX
		Local actualPosY:Float = settings.GridOffset_Y + slider.PosY
				
		If (MouseDown() And slider.IsDown) Or (MouseDown() And MouseY() >= actualPosY - slider.Radius And MouseY() <= actualPosY + slider.Radius And MouseX() >= actualPosX - (slider.Radius / 2) And MouseX() <= actualPosX + slider.Width + (slider.Radius / 2))
			Local clickPosX:Float = MouseX() -actualPosX + (slider.Radius / 2)
			
			slider.Value = (clickPosX) / slider.Width
			slider.IsDown = True
			
			slider.Value = Clamp(slider.Value, slider.MinValue, slider.MaxValue)
			Print slider.Value
		Else
			slider.IsDown = False
		End
		
	End
End



ziggy(Posted 2015) [#3]
I think it's properly done like this. Just, why are you hidding component acces from container class? You can provide readonly access to private component instances by using readonly properties.
I'm not saying you should (hence the demeter principle https://en.wikipedia.org/wiki/Law_of_Demeter ) but, sometimes it's handly if not over abused!


Hero(Posted 2015) [#4]
Hi ziggy,

Thanks for you input. I am just not sure what your suggestion is. Are saying I should make the components readonly properties like so:

	Private
		Field graphicsComponent:IGraphicsComponent = New cSliderGraphicsComponent()
		Field inputComponent:IInputComponent = New cSliderInputComponent()
	
	Public
		Method GraphicsComponent:IGraphicsComponent Property
			Return graphicsComponent
		End
		Method InputComponent:IInputComponent Property
			Return inputComponent
		End


and call the Render and HandleInput from the container class lile:

Class cUITestScene Implements IDrawable, OnValueChangedEvent

	Private
		Field surface:Canvas
		Field alphaSlider:cSlider
		Field redSlider:cSlider
		Field greenSlider:cSlider
		Field blueSlider:cSlider
		Field settings:cSettings = cSettings.Instance()
..				
	Method Update:Void()
		alphaSlider.InputComponent.HandleInput(alphaSlider)
		redSlider.InputComponent.HandleInput(redSlider)
		greenSlider.InputComponent.HandleInput(greenSlider)
		blueSlider.InputComponent.HandleInput(blueSlider)		
	End

	Method Render:Void()
		alphaSlider.GraphicsComponent.Render(alphaSlider)
		redSlider.GraphicsComponent.Render(redSlider)
		greenSlider.GraphicsComponent.Render(greenSlider)
		blueSlider.GraphicsComponent.Render(blueSlider)
	End



?


ziggy(Posted 2015) [#5]
No no, I was not suggesting any change. I was just telling you that your design does not provide access to the components form outside the container class. Which is a good thing if you don't need them, but somehow limiting if you do. All in all, if you don't need it, don't change it!


Hero(Posted 2015) [#6]
Oh I see. Sorry for the confusion.