FYP24006
Lai Yat Kit
Project Member
Dr. Bruno Oliveira
Project Supervisor

Project Plan

Interim Report

Final Report


Haskell is one of the most well-known general-purpose purely functional programming languages. Despite being a fairly exotic language, many applications have been developed in Haskell by its very passionate community.
To develop native desktop graphical user interfaces in Haskell, it is often necessary to utilize third-party libraries.
There are several open-source Haskell libraries created for this purpose, each offering different GUI widget toolkits and employing different GUI programming paradigms.


However, if one hopes to gain from the numerous benefits of designing software with functional programming principles, they might prefer to use a Haskell native desktop GUI library equipped with functional reactive programming as its programming paradigm, complete with a widget toolkit built in pure-Haskell, as opposed to using a typical Haskell library that employs an imperative programming paradigm and is built on top of an external widget toolkit, such as GTK or Qt; or a Haskell library that employs the Elm Architecture, which constrains the maintainability of your application.
Yet currently, such a Haskell library does not exist.
This problem thus motivates the existence of this project. The project aims to develop an implementation for such a library, named RGX, with SDL2, Cairo & Pango to render its graphics, and Reflex as its functional reactive programming library.
The project also demonstrates the capabilities of RGX by building a Syncthing front-end application, named RGXSyncthing.
What does using RGX to create GUIs look like?
buildMainElement :: ::
forall t sig m.
(HasRgxGui t sig m) =>
ElementBuild t m
buildMainElement :: env = mdo
let font = "Sans Serif 24"
-- Accumulate number of clicks
numClicks :: Dynamic t Int <-
foldDyn ($) 0 (btn.clicked $> (+ 1))
-- Create a text label
let
labelText = numClicks <&> \numClicks ->
"Click me! " <> show numClicks <> " click(s)"
labelModel <- newOnelineModel font labelText
-- Create a button
(btn, btnModel) <- newButton Prelude.id labelModel
-- Instantiate
btnModel.build env
buildMainElement ::
forall t sig m.
(HasRgxGui t sig m) =>
ElementBuild t m
buildMainElement env = mdo
let font = "Sans Serif 24"
-- Accumulate number of clicks
numClicks :: Dynamic t Int <-
foldDyn ($) 0 (btn.clicked $> (+ 1))
-- Create a text label
let
labelText = numClicks <&> \numClicks ->
"Click me! " <> show numClicks <> " click(s)"
labelModel <- newOnelineModel font labelText
-- Create a button
(btn, btnModel) <-
newButton Prelude.id
$ positionSimple alignCenterXY
$ labelModel
-- Instantiate
btnModel.build env
buildMainElement ::
forall t sig m.
(HasRgxGui t sig m) =>
ElementBuild t m
buildMainElement env = mdo
let font = "Sans Serif 24"
-- Accumulate number of clicks
numClicks :: Dynamic t Int <-
foldDyn ($) 0 (btn.clicked $> (+ 1))
-- Create a text label
let
labelText =
numClicks <&> \numClicks ->
"Click me! " <> show numClicks <> " click(s)"
labelModel <- newOnelineModel font labelText
-- Create a button
(btn, btnModel) <-
newButton Prelude.id
$ positionSimple alignCenterXY
$ labelModel
-- Add padding + a cyan background for empty spaces
paddedModel <-
pure
$ backgroundSimple (Colour.opaque Colour.cyan)
$ positionSimple (marginAll 32)
$ btnModel
-- Instantiate
paddedModel.build env
# BEFORE
buildMainElement ::
forall t sig m.
(HasRgxGui t sig m) =>
ElementBuild t m
buildMainElement env = mdo
let font = "Sans Serif 24"
-- Accumulate number of clicks
numClicks :: Dynamic t Int <-
foldDyn ($) 0 (btn.clicked $> (+ 1))
-- Create a text label
let
labelText =
numClicks <&> \numClicks ->
"Click me! " <> show numClicks <> " click(s)"
labelModel <- newOnelineModel font labelText
-- Create a button
(btn, btnModel) <-
newButton Prelude.id
$ positionSimple alignCenterXY
$ labelModel
-- Add padding + a cyan background for empty spaces
paddedModel <-
pure
$ backgroundSimple (Colour.opaque Colour.cyan)
$ positionSimple (marginAll 32)
$ btnModel
-- Instantiate
paddedModel.build env
# AFTER
buildMainElement ::
forall t sig m.
(HasRgxGui t sig m) =>
ElementBuild t m
buildMainElement env = do
let font = "Sans Serif 24"
let
newMyButton :: T.Text -> m (Model t m WH)
newMyButton txt = mdo
-- Accumulate number of clicks
numClicks :: Dynamic t Int <-
foldDyn ($) 0 (btn.clicked $> (+ 1))
-- Create a text label
let
labelText =
numClicks <&> \numClicks ->
txt <> show numClicks <> " click(s)"
labelModel <- newOnelineModel font labelText
-- Create a button
(btn, btnModel) <-
newButton Prelude.id
$ positionSimple alignCenterXY
$ labelModel
pure btnModel
-- Create a button
btnModel <- newMyButton "Click me! "
-- Add padding + a cyan background for empty spaces
paddedModel <-
pure
$ backgroundSimple (Colour.opaque Colour.cyan)
$ positionSimple (marginAll 32)
$ btnModel
-- Instantiate
paddedModel.build env
buildMainElement ::
forall t sig m.
(HasRgxGui t sig m) =>
ElementBuild t m
buildMainElement env = do
let font = "Sans Serif 24"
let
newMyButton :: T.Text -> m (Model t m WH)
newMyButton txt = mdo
-- Accumulate number of clicks
numClicks :: Dynamic t Int <-
foldDyn ($) 0 (btn.clicked $> (+ 1))
-- Create a text label
let
labelText =
numClicks <&> \numClicks ->
txt <> show numClicks <> " click(s)"
labelModel <- newOnelineModel font labelText
-- Create a button
(btn, btnModel) <-
newButton Prelude.id
$ positionSimple alignCenterXY
$ labelModel
pure btnModel
-- Create a button
btn1Model <- newMyButton "Click me! "
btn2Model <- newMyButton "Me too! "
btn3Model <- newMyButton "Me three! "
-- Stack all buttons vertically with
-- flexbox "flex" factors 1, 2, & 3.
buttonsModel <-
newLinearLayoutSimple
(constDyn Axis2D.Y)
[ Linear 1 <$> btn1Model
, Linear 2 <$> btn2Model
, Linear 3 <$> btn3Model
]
-- Add padding + a cyan background for empty spaces
paddedModel <-
pure
$ backgroundSimple (Colour.opaque Colour.cyan)
$ positionSimple (marginAll 32)
$ buttonsModel
-- Instantiate
paddedModel.build env
RGX has many useful built-in widgets such as:
You can also create new widgets and extend RGX just by writing pure-Haskell code and using the library Reflex!
(A partial replica of Syncthing’s Official Web UI)