Commit e232aa0f authored by Andrew Gibiansky's avatar Andrew Gibiansky

Merge pull request #519 from sumitsahrawat/refactor

Refactoring Widgets + Boolean Widgets
parents 807f8c97 ef846baa
...@@ -48,7 +48,7 @@ if [ $# -gt 0 ]; then ...@@ -48,7 +48,7 @@ if [ $# -gt 0 ]; then
if [ $1 = "display" ] || [ $1 = "all" ]; then if [ $1 = "display" ] || [ $1 = "all" ]; then
# Install all the display libraries # Install all the display libraries
cd ihaskell-display cd ihaskell-display
for dir in `ls` for dir in `ls | grep -v ihaskell-widgets`
do do
INSTALLS="$INSTALLS ihaskell-display/$dir" INSTALLS="$INSTALLS ihaskell-display/$dir"
done done
...@@ -71,6 +71,10 @@ INSTALL_DIRS=`echo $INSTALLS | tr ' ' '\n' | sed 's#^#./#' | tr ' ' '\n'` ...@@ -71,6 +71,10 @@ INSTALL_DIRS=`echo $INSTALLS | tr ' ' '\n' | sed 's#^#./#' | tr ' ' '\n'`
echo CMD: cabal install --constraint "arithmoi -llvm" -j $INSTALL_DIRS --force-reinstalls --max-backjumps=-1 --reorder-goals echo CMD: cabal install --constraint "arithmoi -llvm" -j $INSTALL_DIRS --force-reinstalls --max-backjumps=-1 --reorder-goals
cabal install --constraint "arithmoi -llvm" -j $INSTALL_DIRS --force-reinstalls --max-backjumps=-1 --reorder-goals cabal install --constraint "arithmoi -llvm" -j $INSTALL_DIRS --force-reinstalls --max-backjumps=-1 --reorder-goals
if [ $1 = "display" ] || [ $1 = "all" ]; then
cabal install ihaskell-display/ihaskell-widgets
fi
if hash ihaskell 2>/dev/null; then if hash ihaskell 2>/dev/null; then
ihaskell install 2>/dev/null || echo "The command \"ihaskell install\" failed. Please check your 'ipython --version'. 3.0 or up is required but it is $(ipython --version)!" ihaskell install 2>/dev/null || echo "The command \"ihaskell install\" failed. Please check your 'ipython --version'. 3.0 or up is required but it is $(ipython --version)!"
else else
......
# IPython widget messaging specification
> Largely based on: https://github.com/ipython/ipython/wiki/IPEP-23:-Backbone.js-Widgets
> The messaging specification as detailed is riddled with the assumptions IHaskell's widget
> implementation makes. It works for us, so it should work for everyone.
## Creating widgets
Let's say the user types in some code, and the only effect of that code is the creation of a widget.
The kernel will open a comm for the widget, and store a reference to that comm inside it. Then, to
notify the frontend about the creation of a widget, an initial state update is sent on the widget's comm.
> The comm should be opened with a `target_name` of `"ipython.widget"`.
The initial state update message looks like this:
```json
{
"method": "update",
"state": { "<some/all widget properties>" }
}
```
Any *numeric* property initialized with the empty string is provided the default value by the frontend.
The initial state update must *at least* have the following fields:
- `msg_throttle` (default 3): To prevent the kernel from flooding with messages, the messages from
the widget to the kernel are throttled. If `msg_throttle` messages were sent, and all are still
processing, the widget will not send anymore state messages.
- `_view_name` (depends on the widget): The frontend uses a generic model to represent
widgets. This field determines how a set of widget properties gets rendered into a
widget. Has the form `IPython.<widgetname>`, e.g `IPython.Button`.
- `_css` (default value = empty list): A list of 3-tuples, (selector, key, value).
- `visible` (default = True): Whether the widget is visible or not.
- Rest of the properties as required initially.
This state update is also used with fragments of the overall state to sync changes between the
frontend and the kernel.
## Displaying widgets
The creation of a widget does not display it. To display a widget, the kernel sends a display
message to the frontend on the widget's comm.
```json
{
"method": "display"
}
```
## Custom messages
* Widgets can also send a custom message, having the form:
```json
{
"method": "custom",
"content": { "<message content>" }
}
```
This message is used by widgets for ad-hoc syncronization, event handling and other stuff. An example
is mentioned in the next section.
## Handling changes to widget in the frontend
Changes to widgets in the frontend lead to messages being sent to the backend. These messages have
two possible formats:
1. Backbone.js initiated sync:
```json
{
"method": "backbone",
"sync_data": { "<changes to sync with the backend>" }
}
```
These messages are sent by the Backbone.js library when some change is made to a widget. For
example, whenever a change is made to the text inside a `TextWidget`, the complete contents are sent
to the kernel so that the kernel stays up-to-date about the widget's contents.
2. Custom message:
```json
{
"method": "custom",
"content": { "<custom message data>" }
}
```
This form is generally used to notify the kernel about events. For example, the `TextWidget` sends a
custom message when the text is submitted by hitting the 'Enter' key.
---
*NOTE*: It's important that the messages sent on the comm are in response to an execution message
from the front-end or another widget's comm message. This is required so the widget framework knows
what cell triggered the message and can display the widget in the correct location.
---
# IHaskell-Widgets
This package implements the [ipython widgets](https://github.com/ipython/ipywidgets) in
IHaskell. The frontend (javascript) is provided by the jupyter/ipython notebook environment, whereas
the backend is implemented in haskell.
To know more about the widget messaging protocol, see [MsgSpec.md](MsgSpec.md).
...@@ -44,7 +44,7 @@ build-type: Simple ...@@ -44,7 +44,7 @@ build-type: Simple
-- Extra files to be distributed with the package, such as examples or a -- Extra files to be distributed with the package, such as examples or a
-- README. -- README.
-- extra-source-files: extra-source-files: README.md, MsgSpec.md
-- Constraint on the version of Cabal needed to build this package. -- Constraint on the version of Cabal needed to build this package.
cabal-version: >=1.10 cabal-version: >=1.10
...@@ -55,25 +55,37 @@ library ...@@ -55,25 +55,37 @@ library
-- Modules included in this library but not exported. -- Modules included in this library but not exported.
other-modules: IHaskell.Display.Widgets.Button other-modules: IHaskell.Display.Widgets.Button
IHaskell.Display.Widgets.Bool.CheckBox
IHaskell.Display.Widgets.Bool.ToggleButton
IHaskell.Display.Widgets.Image IHaskell.Display.Widgets.Image
IHaskell.Display.Widgets.Dropdown IHaskell.Display.Widgets.Output
IHaskell.Display.Widgets.Selection.Dropdown
IHaskell.Display.Widgets.Selection.RadioButtons
IHaskell.Display.Widgets.Selection.Select
IHaskell.Display.Widgets.Selection.ToggleButtons
IHaskell.Display.Widgets.Selection.SelectMultiple
IHaskell.Display.Widgets.String.HTML IHaskell.Display.Widgets.String.HTML
IHaskell.Display.Widgets.String.Latex IHaskell.Display.Widgets.String.Latex
IHaskell.Display.Widgets.String.Text IHaskell.Display.Widgets.String.Text
IHaskell.Display.Widgets.String.TextArea IHaskell.Display.Widgets.String.TextArea
IHaskell.Display.Widgets.Output
IHaskell.Display.Widgets.Types
IHaskell.Display.Widgets.Common IHaskell.Display.Widgets.Common
-- LANGUAGE extensions used by modules in this package. -- LANGUAGE extensions used by modules in this package.
-- other-extensions: -- other-extensions:
-- Other library packages from which modules are imported. -- Other library packages from which modules are imported.
build-depends: aeson >= 0.8.1.0 build-depends: aeson >=0.7 && < 0.9
, base >=4.7 && <4.9 , base >=4.7 && <4.9
, ipython-kernel >= 0.6.1.0 , containers >= 0.5
, text >= 1.2.1.0 , ipython-kernel >= 0.6.1
, unordered-containers >= 0.2.5.1 , text >= 0.11
, unordered-containers -any
, nats -any
, vinyl >= 0.5
, vector -any
, singletons >= 0.9.0
-- Waiting for the next release -- Waiting for the next release
, ihaskell -any , ihaskell -any
...@@ -84,3 +96,10 @@ library ...@@ -84,3 +96,10 @@ library
-- Base language which the package is written in. -- Base language which the package is written in.
default-language: Haskell2010 default-language: Haskell2010
-- Deal with small -fcontext-stack on ghc-7.8.
-- Default values:
-- ghc-7.6.* = 200
-- ghc-7.8.* = 20 -- Too small for vinyl & singletons
-- ghc-7.10.* = 100
if impl(ghc == 7.8.*)
ghc-options: -fcontext-stack=100
...@@ -2,15 +2,23 @@ module IHaskell.Display.Widgets (module X) where ...@@ -2,15 +2,23 @@ module IHaskell.Display.Widgets (module X) where
import IHaskell.Display.Widgets.Button as X import IHaskell.Display.Widgets.Button as X
import IHaskell.Display.Widgets.Dropdown as X import IHaskell.Display.Widgets.Bool.CheckBox as X
import IHaskell.Display.Widgets.Bool.ToggleButton as X
import IHaskell.Display.Widgets.Image as X import IHaskell.Display.Widgets.Image as X
import IHaskell.Display.Widgets.Output as X
import IHaskell.Display.Widgets.Selection.Dropdown as X
import IHaskell.Display.Widgets.Selection.RadioButtons as X
import IHaskell.Display.Widgets.Selection.Select as X
import IHaskell.Display.Widgets.Selection.ToggleButtons as X
import IHaskell.Display.Widgets.Selection.SelectMultiple as X
import IHaskell.Display.Widgets.String.HTML as X import IHaskell.Display.Widgets.String.HTML as X
import IHaskell.Display.Widgets.String.Latex as X import IHaskell.Display.Widgets.String.Latex as X
import IHaskell.Display.Widgets.String.Text as X import IHaskell.Display.Widgets.String.Text as X
import IHaskell.Display.Widgets.String.TextArea as X import IHaskell.Display.Widgets.String.TextArea as X
import IHaskell.Display.Widgets.Output as X import IHaskell.Display.Widgets.Common as X
import IHaskell.Display.Widgets.Types as X (setField, getField)
import IHaskell.Display.Widgets.Common as X (ButtonStyle(..), ImageFormat(..))
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeSynonymInstances #-}
module IHaskell.Display.Widgets.Bool.CheckBox (
-- * The CheckBox Widget
CheckBoxWidget,
-- * Constructor
mkCheckBoxWidget) where
-- To keep `cabal repl` happy when running from the ihaskell repo
import Prelude
import Control.Monad (when, join)
import Data.Aeson
import Data.HashMap.Strict as HM
import Data.IORef (newIORef)
import Data.Text (Text)
import Data.Vinyl (Rec(..), (<+>))
import IHaskell.Display
import IHaskell.Eval.Widgets
import IHaskell.IPython.Message.UUID as U
import IHaskell.Display.Widgets.Types
import IHaskell.Display.Widgets.Common
-- | A 'CheckBoxWidget' represents a Checkbox widget from IPython.html.widgets.
type CheckBoxWidget = IPythonWidget CheckBoxType
-- | Create a new output widget
mkCheckBoxWidget :: IO CheckBoxWidget
mkCheckBoxWidget = do
-- Default properties, with a random uuid
uuid <- U.random
let widgetState = WidgetState $ defaultBoolWidget "CheckboxView"
stateIO <- newIORef widgetState
let widget = IPythonWidget uuid stateIO
initData = object
["model_name" .= str "WidgetModel", "widget_class" .= str "IPython.Checkbox"]
-- Open a comm for this widget, and store it in the kernel state
widgetSendOpen widget initData $ toJSON widgetState
-- Return the image widget
return widget
instance IHaskellDisplay CheckBoxWidget where
display b = do
widgetSendView b
return $ Display []
instance IHaskellWidget CheckBoxWidget where
getCommUUID = uuid
comm widget (Object dict1) _ = do
let key1 = "sync_data" :: Text
key2 = "value" :: Text
Just (Object dict2) = HM.lookup key1 dict1
Just (Bool value) = HM.lookup key2 dict2
setField' widget SBoolValue value
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeSynonymInstances #-}
module IHaskell.Display.Widgets.Bool.ToggleButton (
-- * The ToggleButton Widget
ToggleButton,
-- * Constructor
mkToggleButton) where
-- To keep `cabal repl` happy when running from the ihaskell repo
import Prelude
import Control.Monad (when, join)
import Data.Aeson
import Data.HashMap.Strict as HM
import Data.IORef (newIORef)
import Data.Text (Text)
import Data.Vinyl (Rec(..), (<+>))
import IHaskell.Display
import IHaskell.Eval.Widgets
import IHaskell.IPython.Message.UUID as U
import IHaskell.Display.Widgets.Types
import IHaskell.Display.Widgets.Common
-- | A 'ToggleButton' represents a ToggleButton widget from IPython.html.widgets.
type ToggleButton = IPythonWidget ToggleButtonType
-- | Create a new output widget
mkToggleButton :: IO ToggleButton
mkToggleButton = do
-- Default properties, with a random uuid
uuid <- U.random
let boolState = defaultBoolWidget "ToggleButtonView"
toggleState = (STooltip =:: "")
:& (SIcon =:: "")
:& (SButtonStyle =:: DefaultButton)
:& RNil
widgetState = WidgetState (boolState <+> toggleState)
stateIO <- newIORef widgetState
let widget = IPythonWidget uuid stateIO
initData = object
["model_name" .= str "WidgetModel", "widget_class" .= str "IPython.ToggleButton"]
-- Open a comm for this widget, and store it in the kernel state
widgetSendOpen widget initData $ toJSON widgetState
-- Return the image widget
return widget
instance IHaskellDisplay ToggleButton where
display b = do
widgetSendView b
return $ Display []
instance IHaskellWidget ToggleButton where
getCommUUID = uuid
comm widget (Object dict1) _ = do
let key1 = "sync_data" :: Text
key2 = "value" :: Text
Just (Object dict2) = HM.lookup key1 dict1
Just (Bool value) = HM.lookup key2 dict2
setField' widget SBoolValue value
{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeSynonymInstances #-}
module IHaskell.Display.Widgets.Button ( module IHaskell.Display.Widgets.Button (
-- * The Button Widget -- * The Button Widget
Button, Button,
-- * Create a new button -- * Create a new button
mkButton, mkButton,
-- * Set button properties -- * Click manipulation
setButtonStyle, triggerClick) where
setButtonLabel,
setButtonTooltip,
setButtonStatus,
toggleButtonStatus,
-- * Get button properties
getButtonStyle,
getButtonLabel,
getButtonTooltip,
getButtonStatus,
-- * Click handlers
setClickHandler,
getClickHandler,
triggerClick,
) where
-- To keep `cabal repl` happy when running from the ihaskell repo -- To keep `cabal repl` happy when running from the ihaskell repo
import Prelude import Prelude
import Control.Monad (when) import Control.Monad (when, join)
import Data.Aeson (ToJSON, Value(..), object, toJSON, (.=)) import Data.Aeson
import Data.Aeson.Types (Pair) import Data.HashMap.Strict as HM
import Data.HashMap.Strict as Map import Data.IORef (newIORef)
import Data.IORef
import Data.Text (Text) import Data.Text (Text)
import qualified Data.Text as T import Data.Vinyl (Rec(..), (<+>))
import System.IO.Unsafe (unsafePerformIO)
import IHaskell.Display import IHaskell.Display
import IHaskell.Eval.Widgets import IHaskell.Eval.Widgets
import qualified IHaskell.IPython.Message.UUID as U import IHaskell.IPython.Message.UUID as U
import IHaskell.Types (WidgetMethod(..))
import IHaskell.Display.Widgets.Types
import IHaskell.Display.Widgets.Common import IHaskell.Display.Widgets.Common
-- | A 'Button' represents a Button from IPython.html.widgets. -- | A 'Button' represents a Button from IPython.html.widgets.
data Button = type Button = IPythonWidget ButtonType
Button
{ uuid :: U.UUID -- ^ The UUID for the comm
, description :: IORef Text -- ^ The label displayed on the button
, tooltip :: IORef Text -- ^ The tooltip shown on mouseover
, disabled :: IORef Bool -- ^ Whether the button is disabled
, buttonStyle :: IORef ButtonStyle -- ^ The button_style
, clickHandler :: IORef (Button -> IO ()) -- ^ Function executed when button is clicked
}
-- | Create a new button -- | Create a new button
mkButton :: IO Button mkButton :: IO Button
mkButton = do mkButton = do
-- Default properties, with a random uuid -- Default properties, with a random uuid
commUUID <- U.random uuid <- U.random
desc <- newIORef "label" -- Non-empty to get a display
ttip <- newIORef "" let dom = defaultDOMWidget "ButtonView"
dis <- newIORef False but = (SDescription =:: "")
sty <- newIORef None :& (STooltip =:: "")
fun <- newIORef $ const $ return () :& (SDisabled =:: False)
:& (SIcon =:: "")
let b = Button :& (SButtonStyle =:: DefaultButton)
{ uuid = commUUID :& (SClickHandler =:: return ())
, description = desc :& RNil
, tooltip = ttip buttonState = WidgetState (dom <+> but)
, disabled = dis
, buttonStyle = sty stateIO <- newIORef buttonState
, clickHandler = fun
} let button = IPythonWidget uuid stateIO
let initData = object ["model_name" .= str "WidgetModel", "widget_class" .= str "IPython.Button"]
-- Open a comm for this widget, and store it in the kernel state -- Open a comm for this widget, and store it in the kernel state
widgetSendOpen b (toJSON ButtonInitData) (toJSON b) widgetSendOpen button initData $ toJSON buttonState
-- Return the button widget -- Return the button widget
return b return button
-- | Set the button style
setButtonStyle :: Button -> ButtonStyle -> IO ()
setButtonStyle b bst = do
modify b buttonStyle bst
update b ["button_style" .= bst]
-- | Set the button label
setButtonLabel :: Button -> Text -> IO ()
setButtonLabel b txt = do
modify b description txt
update b ["description" .= txt]
-- | Set the button tooltip
setButtonTooltip :: Button -> Text -> IO ()
setButtonTooltip b txt = do
modify b tooltip txt
update b ["tooltip" .= txt]
-- | Set buttton status. True: Enabled, False: Disabled
setButtonStatus :: Button -> Bool -> IO ()
setButtonStatus b stat = do
let newStatus = not stat
modify b disabled newStatus
update b ["disabled" .= newStatus]
-- | Toggle the button
toggleButtonStatus :: Button -> IO ()
toggleButtonStatus b = do
oldVal <- getButtonStatus b
let newVal = not oldVal
modify b disabled newVal
update b ["disabled" .= newVal]
-- | Get the button style
getButtonStyle :: Button -> IO ButtonStyle
getButtonStyle = readIORef . buttonStyle
-- | Get the button label
getButtonLabel :: Button -> IO Text
getButtonLabel = readIORef . description
-- | Get the button tooltip
getButtonTooltip :: Button -> IO Text
getButtonTooltip = readIORef . tooltip
-- | Check whether the button is enabled / disabled
getButtonStatus :: Button -> IO Bool
getButtonStatus = fmap not . readIORef . disabled
-- | Set a function to be activated on click
setClickHandler :: Button -> (Button -> IO ()) -> IO ()
setClickHandler = writeIORef . clickHandler
-- | Get the click handler for a button
getClickHandler :: Button -> IO (Button -> IO ())
getClickHandler = readIORef . clickHandler
-- | Artificially trigger a button click -- | Artificially trigger a button click
triggerClick :: Button -> IO () triggerClick :: Button -> IO ()
triggerClick button = do triggerClick button = join $ getField button SClickHandler
handler <- getClickHandler button
handler button
data ViewName = ButtonWidget
instance ToJSON ViewName where
toJSON ButtonWidget = "ButtonView"
data InitData = ButtonInitData
instance ToJSON InitData where
toJSON ButtonInitData = object
[ "model_name" .= str "WidgetModel"
, "widget_class" .= str "IPython.Button"
]
instance ToJSON Button where
toJSON b = object
[ "_view_name" .= toJSON ButtonWidget
, "visible" .= True
, "_css" .= object []
, "msg_throttle" .= (3 :: Int)
, "disabled" .= get disabled b
, "description" .= get description b
, "tooltip" .= get tooltip b
, "button_style" .= get buttonStyle b
]
where
get x y = unsafePerformIO . readIORef . x $ y
instance IHaskellDisplay Button where instance IHaskellDisplay Button where
display b = do display b = do
...@@ -179,6 +73,6 @@ instance IHaskellWidget Button where ...@@ -179,6 +73,6 @@ instance IHaskellWidget Button where
comm widget (Object dict1) _ = do comm widget (Object dict1) _ = do
let key1 = "content" :: Text let key1 = "content" :: Text
key2 = "event" :: Text key2 = "event" :: Text
Just (Object dict2) = Map.lookup key1 dict1 Just (Object dict2) = HM.lookup key1 dict1
Just (String event) = Map.lookup key2 dict2 Just (String event) = HM.lookup key2 dict2
when (event == "click") $ triggerClick widget when (event == "click") $ triggerClick widget
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE QuasiQuotes #-}
module IHaskell.Display.Widgets.Common where
module IHaskell.Display.Widgets.Common ( import Data.Aeson
-- * Convenience types import Data.Text (pack, Text)
ButtonStyle(..),
ImageFormat(..), import Data.Singletons.TH
PosInt(..),
-- * Convenience functions (for internal use) -- Widget properties
update, singletons [d|
modify, data Field = ModelModule
str, | ModelName
) where | ViewModule
| ViewName
import Data.Aeson hiding (Success) | MsgThrottle
import Data.Aeson.Types (Pair) | Version
import qualified Data.Text as T | OnDisplayed
import Data.IORef | Visible
| CSS
import IHaskell.Display | DOMClasses
import IHaskell.Eval.Widgets | Width
| Height
-- | Pre-defined button-styles | Padding
data ButtonStyle = Primary | Margin
| Success | Color
| Info | BackgroundColor
| Warning | BorderColor
| Danger | BorderWidth
| None | BorderRadius
deriving (Eq, Show) | BorderStyle
| FontStyle
instance ToJSON ButtonStyle where | FontWeight
toJSON Primary = "primary" | FontSize
toJSON Success = "success" | FontFamily
toJSON Info = "info" | Description
toJSON Warning = "warning" | ClickHandler
toJSON Danger = "danger" | SubmitHandler
toJSON None = "" | Disabled
| StringValue
-- | A wrapper around Int. 'toJSON' gives the no. for positive numbers, and empty string otherwise | Placeholder
newtype PosInt = PosInt { unwrap :: Int } | Tooltip
| Icon
instance ToJSON PosInt where | ButtonStyle
toJSON (PosInt n) | B64Value
| n > 0 = toJSON $ str $ show n | ImageFormat
| otherwise = toJSON $ str $ "" | BoolValue
| Options
| SelectedLabel
| SelectedValue
| SelectionHandler
| Tooltips
| Icons
| SelectedLabels
| SelectedValues
deriving (Eq, Ord, Show)
|]
-- | Pre-defined border styles
data BorderStyleValue = NoBorder
| HiddenBorder
| DottedBorder
| DashedBorder
| SolidBorder
| DoubleBorder
| GrooveBorder
| RidgeBorder
| InsetBorder
| OutsetBorder
| InitialBorder
| InheritBorder
| DefaultBorder
instance ToJSON BorderStyleValue where
toJSON NoBorder = "none"
toJSON HiddenBorder = "hidden"
toJSON DottedBorder = "dotted"
toJSON DashedBorder = "dashed"
toJSON SolidBorder = "solid"
toJSON DoubleBorder = "double"
toJSON GrooveBorder = "groove"
toJSON RidgeBorder = "ridge"
toJSON InsetBorder = "inset"
toJSON OutsetBorder = "outset"
toJSON InitialBorder = "initial"
toJSON InheritBorder = "inherit"
toJSON DefaultBorder = ""
-- | Font style values
data FontStyleValue = NormalFont
| ItalicFont
| ObliqueFont
| InitialFont
| InheritFont
| DefaultFont
instance ToJSON FontStyleValue where
toJSON NormalFont = "normal"
toJSON ItalicFont = "italic"
toJSON ObliqueFont = "oblique"
toJSON InitialFont = "initial"
toJSON InheritFont = "inherit"
toJSON DefaultFont = ""
-- | Font weight values
data FontWeightValue = NormalWeight
| BoldWeight
| BolderWeight
| LighterWeight
| InheritWeight
| InitialWeight
| DefaultWeight
instance ToJSON FontWeightValue where
toJSON NormalWeight = "normal"
toJSON BoldWeight = "bold"
toJSON BolderWeight = "bolder"
toJSON LighterWeight = "lighter"
toJSON InheritWeight = "inherit"
toJSON InitialWeight = "initial"
toJSON DefaultWeight = ""
-- | Pre-defined button styles
data ButtonStyleValue = PrimaryButton
| SuccessButton
| InfoButton
| WarningButton
| DangerButton
| DefaultButton
instance ToJSON ButtonStyleValue where
toJSON PrimaryButton = "primary"
toJSON SuccessButton = "success"
toJSON InfoButton = "info"
toJSON WarningButton = "warning"
toJSON DangerButton = "danger"
toJSON DefaultButton = ""
-- | Image formats for ImageWidget -- | Image formats for ImageWidget
data ImageFormat = PNG data ImageFormatValue = PNG
| SVG | SVG
| JPG | JPG
deriving Eq deriving Eq
instance Show ImageFormat where instance Show ImageFormatValue where
show PNG = "png" show PNG = "png"
show SVG = "svg" show SVG = "svg"
show JPG = "jpg" show JPG = "jpg"
instance ToJSON ImageFormat where instance ToJSON ImageFormatValue where
toJSON = toJSON . T.pack . show toJSON = toJSON . pack . show
-- | Options for selection widgets.
data SelectionOptions = OptionLabels [Text] | OptionDict [(Text, Text)]
-- | Send an update msg for a widget, with custom json. Make it easy to update fragments of the
-- state, by accepting a Pair instead of a Value.
update :: IHaskellWidget a => a -> [Pair] -> IO ()
update widget = widgetSendUpdate widget . toJSON . object
-- | Modify attributes of a widget, stored inside it as IORefs
modify :: IHaskellWidget a => a -> (a -> IORef b) -> b -> IO ()
modify widget attr newval = writeIORef (attr widget) newval
-- | Useful with toJSON
str :: String -> String
str = id
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverloadedStrings #-}
module IHaskell.Display.Widgets.Dropdown (
-- * The dropdown widget
DropdownWidget,
-- * Constructor
mkDropdownWidget,
-- * Set properties
setDropdownText,
setDropdownStatus,
setDropdownOptions,
setDropdownSelected,
-- * Get properties
getDropdownText,
getDropdownStatus,
getDropdownOptions,
getDropdownSelected,
-- * Handle changes
setSelectionHandler,
getSelectionHandler,
triggerSelection,
) where
-- To keep `cabal repl` happy when running from the ihaskell repo
import Prelude
import Control.Monad (when)
import Data.Aeson (ToJSON, Value(..), object, toJSON, (.=))
import Data.Aeson.Types (Pair)
import Data.HashMap.Strict as Map
import Data.IORef
import Data.Text (Text)
import System.IO.Unsafe (unsafePerformIO)
import IHaskell.Display
import IHaskell.Eval.Widgets
import qualified IHaskell.IPython.Message.UUID as U
import IHaskell.Display.Widgets.Common
-- | A 'Dropdown' represents a Dropdown widget from IPython.html.widgets.
data DropdownWidget =
DropdownWidget
{ uuid :: U.UUID -- ^ The UUID for the comm
, description :: IORef Text -- ^ The label displayed beside the dropdown
, disabled :: IORef Bool -- ^ Whether the dropdown is disabled
, selectedLabel :: IORef Text -- ^ The label which is currently selected
, labelOptions :: IORef [Text] -- ^ The possible label options
, selectionHandler :: IORef (DropdownWidget -> IO ())
}
-- | Create a new dropdown
mkDropdownWidget :: IO DropdownWidget
mkDropdownWidget = do
-- Default properties, with a random uuid
commUUID <- U.random
desc <- newIORef ""
dis <- newIORef False
sel <- newIORef ""
opts <- newIORef []
handler <- newIORef $ const $ return ()
let b = DropdownWidget
{ uuid = commUUID
, description = desc
, disabled = dis
, selectedLabel = sel
, labelOptions = opts
, selectionHandler = handler
}
let initData = object
["model_name" .= str "WidgetModel", "widget_class" .= str "IPython.Dropdown"]
-- Open a comm for this widget, and store it in the kernel state
widgetSendOpen b initData $ toJSON b
-- Return the dropdown widget
return b
setDropdownText :: DropdownWidget -> Text -> IO ()
setDropdownText widget text = do
modify widget description text
update widget ["description" .= text]
setDropdownStatus :: DropdownWidget -> Bool -> IO ()
setDropdownStatus widget stat = do
let newStat = not stat
modify widget disabled newStat
update widget ["disabled" .= newStat]
setDropdownOptions :: DropdownWidget -> [Text] -> IO ()
setDropdownOptions widget opts = do
modify widget labelOptions opts
update widget ["_options_labels" .= opts]
setDropdownSelected :: DropdownWidget -> Text -> IO ()
setDropdownSelected widget opt = do
possibleOpts <- getDropdownOptions widget
when (opt `elem` possibleOpts) $ do
modify widget selectedLabel opt
update widget ["selected_label" .= opt]
triggerSelection widget
toggleDropdownStatus :: DropdownWidget -> IO ()
toggleDropdownStatus widget = modifyIORef (disabled widget) not
getDropdownText :: DropdownWidget -> IO Text
getDropdownText = readIORef . description
getDropdownStatus :: DropdownWidget -> IO Bool
getDropdownStatus = fmap not . readIORef . disabled
getDropdownOptions :: DropdownWidget -> IO [Text]
getDropdownOptions = readIORef . labelOptions
getDropdownSelected :: DropdownWidget -> IO Text
getDropdownSelected = readIORef . selectedLabel
-- | Set a function to be activated on selection
setSelectionHandler :: DropdownWidget -> (DropdownWidget -> IO ()) -> IO ()
setSelectionHandler = writeIORef . selectionHandler
-- | Get the selection handler for a dropdown
getSelectionHandler :: DropdownWidget -> IO (DropdownWidget -> IO ())
getSelectionHandler = readIORef . selectionHandler
-- | Artificially trigger a selection
triggerSelection :: DropdownWidget -> IO ()
triggerSelection widget = do
handler <- getSelectionHandler widget
handler widget
instance ToJSON DropdownWidget where
toJSON b = object
[ "_view_name" .= str "DropdownView"
, "visible" .= True
, "_css" .= object []
, "msg_throttle" .= (3 :: Int)
, "disabled" .= get disabled b
, "description" .= get description b
, "_options_labels" .= get labelOptions b
, "selected_label" .= get selectedLabel b
, "button_style" .= str ""
]
where
get x y = unsafePerformIO . readIORef . x $ y
instance IHaskellDisplay DropdownWidget where
display b = do
widgetSendView b
return $ Display []
instance IHaskellWidget DropdownWidget where
getCommUUID = uuid
comm widget (Object dict1) _ = do
let key1 = "sync_data" :: Text
key2 = "selected_label" :: Text
Just (Object dict2) = Map.lookup key1 dict1
Just (String label) = Map.lookup key2 dict2
modify widget selectedLabel label
triggerSelection widget
{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeSynonymInstances #-}
module IHaskell.Display.Widgets.Image ( module IHaskell.Display.Widgets.Image (
-- * The Image Widget -- * The Image Widget
ImageWidget, ImageWidget,
-- * Create a new image widget -- * Constructor
mkImageWidget, mkImageWidget) where
-- * Set image properties
setImageFormat,
setImageB64Value,
setImageWidth,
setImageHeight,
-- * Get image properties
getImageFormat,
getImageB64Value,
getImageWidth,
getImageHeight,
) where
-- To keep `cabal repl` happy when running from the ihaskell repo -- To keep `cabal repl` happy when running from the ihaskell repo
import Prelude import Prelude
import Control.Monad (when) import Control.Monad (when, join)
import Data.Aeson (ToJSON, Value(..), object, toJSON, (.=)) import Data.Aeson
import Data.Aeson.Types (Pair) import Data.HashMap.Strict as HM
import Data.HashMap.Strict as Map import Data.IORef (newIORef)
import Data.IORef import Data.Monoid (mempty)
import Data.Text (Text) import Data.Text (Text)
import qualified Data.Text as T import Data.Vinyl (Rec(..), (<+>))
import System.IO.Unsafe (unsafePerformIO)
import IHaskell.Display import IHaskell.Display
import IHaskell.Eval.Widgets import IHaskell.Eval.Widgets
import qualified IHaskell.IPython.Message.UUID as U import IHaskell.IPython.Message.UUID as U
import IHaskell.Types (WidgetMethod(..))
import IHaskell.Display.Widgets.Types
import IHaskell.Display.Widgets.Common import IHaskell.Display.Widgets.Common
-- | A 'Image' represents a Image from IPython.html.widgets. -- | An 'ImageWidget' represents a Image widget from IPython.html.widgets.
data ImageWidget = type ImageWidget = IPythonWidget ImageType
ImageWidget
{ uuid :: U.UUID
, format :: IORef ImageFormat
, height :: IORef PosInt
, width :: IORef PosInt
, b64value :: IORef Base64
}
-- | Create a new image widget -- | Create a new image widget
mkImageWidget :: IO ImageWidget mkImageWidget :: IO ImageWidget
mkImageWidget = do mkImageWidget = do
-- Default properties, with a random uuid -- Default properties, with a random uuid
commUUID <- U.random uuid <- U.random
fmt <- newIORef PNG
hgt <- newIORef (PosInt 0)
wdt <- newIORef (PosInt 0)
val <- newIORef ""
let initData = object ["model_name" .= str "WidgetModel", "widget_class" .= str "IPython.Image"] let dom = defaultDOMWidget "ImageView"
b = ImageWidget { uuid = commUUID, format = fmt, height = hgt, width = wdt, b64value = val } img = (SImageFormat =:: PNG)
:& (SB64Value =:: mempty)
-- Open a comm for this widget, and store it in the kernel state :& RNil
widgetSendOpen b initData (toJSON b) widgetState = WidgetState (dom <+> img)
-- Return the image widget
return b
-- | Set the image style
setImageFormat :: ImageWidget -> ImageFormat -> IO ()
setImageFormat b fmt = do
modify b format fmt
update b ["format" .= fmt]
-- | Set the image value (encoded in base64) stateIO <- newIORef widgetState
setImageB64Value :: ImageWidget -> Base64 -> IO ()
setImageB64Value b val = do
modify b b64value val
update b ["_b64value" .= val]
-- | Set the image width let widget = IPythonWidget uuid stateIO
setImageWidth :: ImageWidget -> Int -> IO ()
setImageWidth b wdt = do
let w = PosInt wdt
modify b width w
update b ["width" .= w]
-- | Set the image height let initData = object ["model_name" .= str "WidgetModel", "widget_class" .= str "IPython.Image"]
setImageHeight :: ImageWidget -> Int -> IO ()
setImageHeight b hgt = do
let h = PosInt hgt
modify b height h
update b ["height" .= h]
-- | Get the image format
getImageFormat :: ImageWidget -> IO ImageFormat
getImageFormat = readIORef . format
-- | Get the image value (encoded in base64)
getImageB64Value :: ImageWidget -> IO Base64
getImageB64Value = readIORef . b64value
-- | Get the image width
getImageWidth :: ImageWidget -> IO Int
getImageWidth = fmap unwrap . readIORef . width
-- | Get the image height -- Open a comm for this widget, and store it in the kernel state
getImageHeight :: ImageWidget -> IO Int widgetSendOpen widget initData $ toJSON widgetState
getImageHeight = fmap unwrap . readIORef . height
instance ToJSON ImageWidget where -- Return the image widget
toJSON b = object return widget
[ "_view_module" .= str ""
, "background_color" .= str ""
, "border_width" .= str ""
, "border_color" .= str ""
, "width" .= get width b
, "_dom_classes" .= object []
, "margin" .= str ""
, "font_style" .= str ""
, "font_weight" .= str ""
, "height" .= get height b
, "font_size" .= str ""
, "border_style" .= str ""
, "padding" .= str ""
, "border_radius" .= str ""
, "version" .= (0 :: Int)
, "font_family" .= str ""
, "color" .= str ""
, "_view_name" .= str "ImageView"
, "visible" .= True
, "_css" .= object []
, "msg_throttle" .= (3 :: Int)
, "format" .= get format b
, "_b64value" .= get b64value b
]
where
get x y = unsafePerformIO . readIORef . x $ y
instance IHaskellDisplay ImageWidget where instance IHaskellDisplay ImageWidget where
display b = do display b = do
......
{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeSynonymInstances #-}
module IHaskell.Display.Widgets.Output ( module IHaskell.Display.Widgets.Output (
-- * The Output Widget -- * The Output Widget
OutputWidget, OutputWidget,
-- * Constructor -- * Constructor
mkOutputWidget, mkOutputWidget,
-- * Get/Set/Modify width -- * Using the output widget
getOutputWidth,
setOutputWidth,
modifyOutputWidth,
modifyOutputWidth_,
-- * Output to widget
appendOutput, appendOutput,
clearOutput, clearOutput,
clearOutput_, clearOutput_,
...@@ -21,62 +18,40 @@ module IHaskell.Display.Widgets.Output ( ...@@ -21,62 +18,40 @@ module IHaskell.Display.Widgets.Output (
-- To keep `cabal repl` happy when running from the ihaskell repo -- To keep `cabal repl` happy when running from the ihaskell repo
import Prelude import Prelude
import Control.Monad (when, void) import Control.Monad (when, join)
import Data.Aeson (ToJSON, Value(..), object, toJSON, (.=)) import Data.Aeson
import Data.Aeson.Types (Pair, Array) import Data.HashMap.Strict as HM
import qualified Data.HashMap.Strict as Map import Data.IORef (newIORef)
import Data.IORef
import Data.Text (Text) import Data.Text (Text)
import qualified Data.Text as T import Data.Vinyl (Rec(..), (<+>))
import System.IO.Unsafe (unsafePerformIO)
import IHaskell.Display import IHaskell.Display
import IHaskell.Eval.Widgets import IHaskell.Eval.Widgets
import qualified IHaskell.IPython.Message.UUID as U import IHaskell.IPython.Message.UUID as U
import IHaskell.Types (WidgetMethod(..))
import IHaskell.Display.Widgets.Common import IHaskell.Display.Widgets.Types
data OutputWidget = OutputWidget { uuid :: U.UUID, width :: IORef PosInt } -- | An 'OutputWidget' represents a Output widget from IPython.html.widgets.
type OutputWidget = IPythonWidget OutputType
-- | Create a new output widget
mkOutputWidget :: IO OutputWidget mkOutputWidget :: IO OutputWidget
mkOutputWidget = do mkOutputWidget = do
-- Default properties, with a random uuid -- Default properties, with a random uuid
commUUID <- U.random uuid <- U.random
wdt <- newIORef $ PosInt 400
dis <- newIORef False
let b = OutputWidget { uuid = commUUID, width = wdt } let widgetState = WidgetState $ defaultDOMWidget "OutputView"
let initData = object ["model_name" .= str "WidgetModel"] stateIO <- newIORef widgetState
-- Open a comm for this widget, and store it in the kernel state let widget = IPythonWidget uuid stateIO
widgetSendOpen b initData (toJSON b) initData = object ["model_name" .= str "WidgetModel"]
-- Return the widget
return b
-- | Get the output widget width
getOutputWidth :: OutputWidget -> IO Int
getOutputWidth = fmap unwrap . readIORef . width
-- | Set the output widget width -- Open a comm for this widget, and store it in the kernel state
setOutputWidth :: OutputWidget -> Int -> IO () widgetSendOpen widget initData $ toJSON widgetState
setOutputWidth widget widthInt = do
let w = PosInt widthInt
modify widget width w
update widget ["width" .= w]
-- | Modify the output widget width (with IO)
modifyOutputWidth :: OutputWidget -> (Int -> IO Int) -> IO ()
modifyOutputWidth widget modifier = getOutputWidth widget >>= modifier >>= setOutputWidth widget
-- | Modify the output widget width (with pure modifier) -- Return the image widget
modifyOutputWidth_ :: OutputWidget -> (Int -> Int) -> IO () return widget
modifyOutputWidth_ widget modifier = do
w <- getOutputWidth widget
let newWidth = modifier w
setOutputWidth widget newWidth
-- | Append to the output widget -- | Append to the output widget
appendOutput :: IHaskellDisplay a => OutputWidget -> a -> IO () appendOutput :: IHaskellDisplay a => OutputWidget -> a -> IO ()
...@@ -98,33 +73,6 @@ replaceOutput widget d = do ...@@ -98,33 +73,6 @@ replaceOutput widget d = do
clearOutput_ widget clearOutput_ widget
appendOutput widget d appendOutput widget d
instance ToJSON OutputWidget where
toJSON b = object
[ "_view_module" .= str ""
, "background_color" .= str ""
, "border_width" .= str ""
, "border_color" .= str ""
, "width" .= get width b
, "_dom_classes" .= object []
, "margin" .= str ""
, "font_style" .= str ""
, "font_weight" .= str ""
, "height" .= str ""
, "font_size" .= str ""
, "border_style" .= str ""
, "padding" .= str ""
, "border_radius" .= str ""
, "version" .= (0 :: Int)
, "font_family" .= str ""
, "color" .= str ""
, "_view_name" .= str "OutputView"
, "visible" .= True
, "_css" .= object []
, "msg_throttle" .= (3 :: Int)
]
where
get x = unsafePerformIO . readIORef . x
instance IHaskellDisplay OutputWidget where instance IHaskellDisplay OutputWidget where
display b = do display b = do
widgetSendView b widgetSendView b
......
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeSynonymInstances #-}
module IHaskell.Display.Widgets.Selection.Dropdown (
-- * The Dropdown Widget
Dropdown,
-- * Constructor
mkDropdown) where
-- To keep `cabal repl` happy when running from the ihaskell repo
import Prelude
import Control.Monad (when, join)
import Data.Aeson
import qualified Data.HashMap.Strict as HM
import Data.IORef (newIORef)
import Data.Text (Text)
import Data.Vinyl (Rec(..), (<+>))
import IHaskell.Display
import IHaskell.Eval.Widgets
import IHaskell.IPython.Message.UUID as U
import IHaskell.Display.Widgets.Types
import IHaskell.Display.Widgets.Common
-- | A 'Dropdown' represents a Dropdown widget from IPython.html.widgets.
type Dropdown = IPythonWidget DropdownType
-- | Create a new Dropdown widget
mkDropdown :: IO Dropdown
mkDropdown = do
-- Default properties, with a random uuid
uuid <- U.random
let selectionAttrs = defaultSelectionWidget "DropdownView"
dropdownAttrs = (SButtonStyle =:: DefaultButton) :& RNil
widgetState = WidgetState $ selectionAttrs <+> dropdownAttrs
stateIO <- newIORef widgetState
let widget = IPythonWidget uuid stateIO
initData = object
["model_name" .= str "WidgetModel", "widget_class" .= str "IPython.Dropdown"]
-- Open a comm for this widget, and store it in the kernel state
widgetSendOpen widget initData $ toJSON widgetState
-- Return the widget
return widget
-- | Artificially trigger a selection
triggerSelection :: Dropdown -> IO ()
triggerSelection widget = join $ getField widget SSelectionHandler
instance IHaskellDisplay Dropdown where
display b = do
widgetSendView b
return $ Display []
instance IHaskellWidget Dropdown where
getCommUUID = uuid
comm widget (Object dict1) _ = do
let key1 = "sync_data" :: Text
key2 = "selected_label" :: Text
Just (Object dict2) = HM.lookup key1 dict1
Just (String label) = HM.lookup key2 dict2
opts <- getField widget SOptions
case opts of
OptionLabels _ -> do
setField' widget SSelectedLabel label
setField' widget SSelectedValue label
OptionDict ps ->
case lookup label ps of
Nothing -> return ()
Just value -> do
setField' widget SSelectedLabel label
setField' widget SSelectedValue value
triggerSelection widget
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeSynonymInstances #-}
module IHaskell.Display.Widgets.Selection.RadioButtons (
-- * The RadioButtons Widget
RadioButtons,
-- * Constructor
mkRadioButtons) where
-- To keep `cabal repl` happy when running from the ihaskell repo
import Prelude
import Control.Monad (when, join)
import Data.Aeson
import qualified Data.HashMap.Strict as HM
import Data.IORef (newIORef)
import Data.Text (Text)
import Data.Vinyl (Rec(..), (<+>))
import IHaskell.Display
import IHaskell.Eval.Widgets
import IHaskell.IPython.Message.UUID as U
import IHaskell.Display.Widgets.Types
import IHaskell.Display.Widgets.Common
-- | A 'RadioButtons' represents a RadioButtons widget from IPython.html.widgets.
type RadioButtons = IPythonWidget RadioButtonsType
-- | Create a new RadioButtons widget
mkRadioButtons :: IO RadioButtons
mkRadioButtons = do
-- Default properties, with a random uuid
uuid <- U.random
let widgetState = WidgetState $ defaultSelectionWidget "RadioButtonsView"
stateIO <- newIORef widgetState
let widget = IPythonWidget uuid stateIO
initData = object
["model_name" .= str "WidgetModel", "widget_class" .= str "IPython.RadioButtons"]
-- Open a comm for this widget, and store it in the kernel state
widgetSendOpen widget initData $ toJSON widgetState
-- Return the widget
return widget
-- | Artificially trigger a selection
triggerSelection :: RadioButtons -> IO ()
triggerSelection widget = join $ getField widget SSelectionHandler
instance IHaskellDisplay RadioButtons where
display b = do
widgetSendView b
return $ Display []
instance IHaskellWidget RadioButtons where
getCommUUID = uuid
comm widget (Object dict1) _ = do
let key1 = "sync_data" :: Text
key2 = "selected_label" :: Text
Just (Object dict2) = HM.lookup key1 dict1
Just (String label) = HM.lookup key2 dict2
opts <- getField widget SOptions
case opts of
OptionLabels _ -> do
setField' widget SSelectedLabel label
setField' widget SSelectedValue label
OptionDict ps ->
case lookup label ps of
Nothing -> return ()
Just value -> do
setField' widget SSelectedLabel label
setField' widget SSelectedValue value
triggerSelection widget
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeSynonymInstances #-}
module IHaskell.Display.Widgets.Selection.Select (
-- * The Select Widget
SelectWidget,
-- * Constructor
mkSelectWidget) where
-- To keep `cabal repl` happy when running from the ihaskell repo
import Prelude
import Control.Monad (when, join)
import Data.Aeson
import qualified Data.HashMap.Strict as HM
import Data.IORef (newIORef)
import Data.Text (Text)
import Data.Vinyl (Rec(..), (<+>))
import IHaskell.Display
import IHaskell.Eval.Widgets
import IHaskell.IPython.Message.UUID as U
import IHaskell.Display.Widgets.Types
import IHaskell.Display.Widgets.Common
-- | A 'SelectWidget' represents a Select widget from IPython.html.widgets.
type SelectWidget = IPythonWidget SelectType
-- | Create a new Select widget
mkSelectWidget :: IO SelectWidget
mkSelectWidget = do
-- Default properties, with a random uuid
uuid <- U.random
let widgetState = WidgetState $ defaultSelectionWidget "SelectView"
stateIO <- newIORef widgetState
let widget = IPythonWidget uuid stateIO
initData = object ["model_name" .= str "WidgetModel", "widget_class" .= str "IPython.Select"]
-- Open a comm for this widget, and store it in the kernel state
widgetSendOpen widget initData $ toJSON widgetState
-- Return the widget
return widget
-- | Artificially trigger a selection
triggerSelection :: SelectWidget -> IO ()
triggerSelection widget = join $ getField widget SSelectionHandler
instance IHaskellDisplay SelectWidget where
display b = do
widgetSendView b
return $ Display []
instance IHaskellWidget SelectWidget where
getCommUUID = uuid
comm widget (Object dict1) _ = do
let key1 = "sync_data" :: Text
key2 = "selected_label" :: Text
Just (Object dict2) = HM.lookup key1 dict1
Just (String label) = HM.lookup key2 dict2
opts <- getField widget SOptions
case opts of
OptionLabels _ -> do
setField' widget SSelectedLabel label
setField' widget SSelectedValue label
OptionDict ps ->
case lookup label ps of
Nothing -> return ()
Just value -> do
setField' widget SSelectedLabel label
setField' widget SSelectedValue value
triggerSelection widget
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeSynonymInstances #-}
module IHaskell.Display.Widgets.Selection.SelectMultiple (
-- * The SelectMultiple Widget
SelectMultipleWidget,
-- * Constructor
mkSelectMultipleWidget) where
-- To keep `cabal repl` happy when running from the ihaskell repo
import Prelude
import Control.Monad (fmap, join, sequence)
import Data.Aeson
import qualified Data.HashMap.Strict as HM
import Data.IORef (newIORef)
import Data.Text (Text)
import qualified Data.Vector as V
import Data.Vinyl (Rec(..), (<+>))
import IHaskell.Display
import IHaskell.Eval.Widgets
import IHaskell.IPython.Message.UUID as U
import IHaskell.Display.Widgets.Types
import IHaskell.Display.Widgets.Common
-- | A 'SelectMultipleWidget' represents a SelectMultiple widget from IPython.html.widgets.
type SelectMultipleWidget = IPythonWidget SelectMultipleType
-- | Create a new SelectMultiple widget
mkSelectMultipleWidget :: IO SelectMultipleWidget
mkSelectMultipleWidget = do
-- Default properties, with a random uuid
uuid <- U.random
let widgetState = WidgetState $ defaultMultipleSelectionWidget "SelectMultipleView"
stateIO <- newIORef widgetState
let widget = IPythonWidget uuid stateIO
initData = object
[ "model_name" .= str "WidgetModel"
, "widget_class" .= str "IPython.SelectMultiple"
]
-- Open a comm for this widget, and store it in the kernel state
widgetSendOpen widget initData $ toJSON widgetState
-- Return the widget
return widget
-- | Artificially trigger a selection
triggerSelection :: SelectMultipleWidget -> IO ()
triggerSelection widget = join $ getField widget SSelectionHandler
instance IHaskellDisplay SelectMultipleWidget where
display b = do
widgetSendView b
return $ Display []
instance IHaskellWidget SelectMultipleWidget where
getCommUUID = uuid
comm widget (Object dict1) _ = do
let key1 = "sync_data" :: Text
key2 = "selected_labels" :: Text
Just (Object dict2) = HM.lookup key1 dict1
Just (Array labels) = HM.lookup key2 dict2
labelList = map (\(String x) -> x) $ V.toList labels
opts <- getField widget SOptions
case opts of
OptionLabels _ -> do
setField' widget SSelectedLabels labelList
setField' widget SSelectedValues labelList
OptionDict ps ->
case sequence $ map (`lookup` ps) labelList of
Nothing -> return ()
Just valueList -> do
setField' widget SSelectedLabels labelList
setField' widget SSelectedValues valueList
triggerSelection widget
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeSynonymInstances #-}
module IHaskell.Display.Widgets.Selection.ToggleButtons (
-- * The ToggleButtons Widget
ToggleButtons,
-- * Constructor
mkToggleButtons) where
-- To keep `cabal repl` happy when running from the ihaskell repo
import Prelude
import Control.Monad (when, join)
import Data.Aeson
import qualified Data.HashMap.Strict as HM
import Data.IORef (newIORef)
import Data.Text (Text)
import Data.Vinyl (Rec(..), (<+>))
import IHaskell.Display
import IHaskell.Eval.Widgets
import IHaskell.IPython.Message.UUID as U
import IHaskell.Display.Widgets.Types
import IHaskell.Display.Widgets.Common
-- | A 'ToggleButtons' represents a ToggleButtons widget from IPython.html.widgets.
type ToggleButtons = IPythonWidget ToggleButtonsType
-- | Create a new ToggleButtons widget
mkToggleButtons :: IO ToggleButtons
mkToggleButtons = do
-- Default properties, with a random uuid
uuid <- U.random
let selectionAttrs = defaultSelectionWidget "ToggleButtonsView"
toggleButtonsAttrs = (STooltips =:: [])
:& (SIcons =:: [])
:& (SButtonStyle =:: DefaultButton)
:& RNil
widgetState = WidgetState $ selectionAttrs <+> toggleButtonsAttrs
stateIO <- newIORef widgetState
let widget = IPythonWidget uuid stateIO
initData = object
[ "model_name" .= str "WidgetModel"
, "widget_class" .= str "IPython.ToggleButtons"
]
-- Open a comm for this widget, and store it in the kernel state
widgetSendOpen widget initData $ toJSON widgetState
-- Return the widget
return widget
-- | Artificially trigger a selection
triggerSelection :: ToggleButtons -> IO ()
triggerSelection widget = join $ getField widget SSelectionHandler
instance IHaskellDisplay ToggleButtons where
display b = do
widgetSendView b
return $ Display []
instance IHaskellWidget ToggleButtons where
getCommUUID = uuid
comm widget (Object dict1) _ = do
let key1 = "sync_data" :: Text
key2 = "selected_label" :: Text
Just (Object dict2) = HM.lookup key1 dict1
Just (String label) = HM.lookup key2 dict2
opts <- getField widget SOptions
case opts of
OptionLabels _ -> do
setField' widget SSelectedLabel label
setField' widget SSelectedValue label
OptionDict ps ->
case lookup label ps of
Nothing -> return ()
Just value -> do
setField' widget SSelectedLabel label
setField' widget SSelectedValue value
triggerSelection widget
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeSynonymInstances #-}
module IHaskell.Display.Widgets.String.HTML ( module IHaskell.Display.Widgets.String.HTML (
-- * The HTML Widget -- * The HTML Widget
HTMLWidget, HTMLWidget,
-- * Constructor -- * Constructor
mkHTMLWidget, mkHTMLWidget) where
-- * Set properties
setHTMLValue,
setHTMLDescription,
setHTMLPlaceholder,
-- * Get properties
getHTMLValue,
getHTMLDescription,
getHTMLPlaceholder,
) where
-- To keep `cabal repl` happy when running from the ihaskell repo -- To keep `cabal repl` happy when running from the ihaskell repo
import Prelude import Prelude
import Control.Monad (when) import Control.Monad (when, join)
import Data.Aeson (ToJSON, Value(..), object, toJSON, (.=)) import Data.Aeson
import Data.Aeson.Types (Pair) import Data.IORef (newIORef)
import Data.HashMap.Strict as Map
import Data.IORef
import Data.Text (Text) import Data.Text (Text)
import qualified Data.Text as T import Data.Vinyl (Rec(..), (<+>))
import System.IO.Unsafe (unsafePerformIO)
import IHaskell.Display import IHaskell.Display
import IHaskell.Eval.Widgets import IHaskell.Eval.Widgets
import qualified IHaskell.IPython.Message.UUID as U import IHaskell.IPython.Message.UUID as U
import IHaskell.Display.Widgets.Common import IHaskell.Display.Widgets.Types
data HTMLWidget = -- | A 'HTMLWidget' represents a HTML widget from IPython.html.widgets.
HTMLWidget type HTMLWidget = IPythonWidget HTMLType
{ uuid :: U.UUID
, value :: IORef Text
, description :: IORef Text
, placeholder :: IORef Text
}
-- | Create a new HTML widget -- | Create a new HTML widget
mkHTMLWidget :: IO HTMLWidget mkHTMLWidget :: IO HTMLWidget
mkHTMLWidget = do mkHTMLWidget = do
-- Default properties, with a random uuid -- Default properties, with a random uuid
commUUID <- U.random uuid <- U.random
val <- newIORef "" let widgetState = WidgetState $ defaultStringWidget "HTMLView"
des <- newIORef ""
plc <- newIORef ""
let b = HTMLWidget { uuid = commUUID, value = val, description = des, placeholder = plc } stateIO <- newIORef widgetState
let initData = object ["model_name" .= str "WidgetModel", "widget_class" .= str "IPython.HTML"] let widget = IPythonWidget uuid stateIO
initData = object ["model_name" .= str "WidgetModel", "widget_class" .= str "IPython.HTML"]
-- Open a comm for this widget, and store it in the kernel state -- Open a comm for this widget, and store it in the kernel state
widgetSendOpen b initData (toJSON b) widgetSendOpen widget initData $ toJSON widgetState
-- Return the string widget -- Return the widget
return b return widget
-- | Set the HTML string value.
setHTMLValue :: HTMLWidget -> Text -> IO ()
setHTMLValue b txt = do
modify b value txt
update b ["value" .= txt]
-- | Set the HTML description
setHTMLDescription :: HTMLWidget -> Text -> IO ()
setHTMLDescription b txt = do
modify b description txt
update b ["description" .= txt]
-- | Set the HTML placeholder, i.e. text displayed in empty widget
setHTMLPlaceholder :: HTMLWidget -> Text -> IO ()
setHTMLPlaceholder b txt = do
modify b placeholder txt
update b ["placeholder" .= txt]
-- | Get the HTML string value.
getHTMLValue :: HTMLWidget -> IO Text
getHTMLValue = readIORef . value
-- | Get the HTML description value.
getHTMLDescription :: HTMLWidget -> IO Text
getHTMLDescription = readIORef . description
-- | Get the HTML placeholder value.
getHTMLPlaceholder :: HTMLWidget -> IO Text
getHTMLPlaceholder = readIORef . placeholder
instance ToJSON HTMLWidget where
toJSON b = object
[ "_view_name" .= str "HTMLView"
, "visible" .= True
, "_css" .= object []
, "msg_throttle" .= (3 :: Int)
, "value" .= get value b
]
where
get x y = unsafePerformIO . readIORef . x $ y
instance IHaskellDisplay HTMLWidget where instance IHaskellDisplay HTMLWidget where
display b = do display b = do
......
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeSynonymInstances #-}
module IHaskell.Display.Widgets.String.Latex ( module IHaskell.Display.Widgets.String.Latex (
-- * The Latex Widget -- * The Latex Widget
LatexWidget, LatexWidget,
-- * Constructor -- * Constructor
mkLatexWidget, mkLatexWidget) where
-- * Set properties
setLatexValue,
setLatexPlaceholder,
setLatexDescription,
setLatexWidth,
-- * Get properties
getLatexValue,
getLatexPlaceholder,
getLatexDescription,
getLatexWidth,
) where
-- To keep `cabal repl` happy when running from the ihaskell repo -- To keep `cabal repl` happy when running from the ihaskell repo
import Prelude import Prelude
import Control.Monad (when) import Control.Monad (when, join)
import Data.Aeson (ToJSON, Value(..), object, toJSON, (.=)) import Data.Aeson
import Data.Aeson.Types (Pair) import Data.IORef (newIORef)
import Data.HashMap.Strict as Map
import Data.IORef
import Data.Text (Text) import Data.Text (Text)
import qualified Data.Text as T import Data.Vinyl (Rec(..), (<+>))
import System.IO.Unsafe (unsafePerformIO)
import IHaskell.Display import IHaskell.Display
import IHaskell.Eval.Widgets import IHaskell.Eval.Widgets
import qualified IHaskell.IPython.Message.UUID as U import IHaskell.IPython.Message.UUID as U
import IHaskell.Display.Widgets.Common import IHaskell.Display.Widgets.Types
data LatexWidget = -- | A 'LatexWidget' represents a Latex widget from IPython.html.widgets.
LatexWidget type LatexWidget = IPythonWidget LatexType
{ uuid :: U.UUID
, value :: IORef Text
, description :: IORef Text
, placeholder :: IORef Text
, width :: IORef Int
}
-- | Create a new Latex widget -- | Create a new Latex widget
mkLatexWidget :: IO LatexWidget mkLatexWidget :: IO LatexWidget
mkLatexWidget = do mkLatexWidget = do
-- Default properties, with a random uuid -- Default properties, with a random uuid
commUUID <- U.random uuid <- U.random
val <- newIORef "" let widgetState = WidgetState $ defaultStringWidget "LatexView"
des <- newIORef ""
plc <- newIORef ""
width <- newIORef 400
let b = LatexWidget stateIO <- newIORef widgetState
{ uuid = commUUID
, value = val
, description = des
, placeholder = plc
, width = width
}
let initData = object ["model_name" .= str "WidgetModel", "widget_class" .= str "IPython.Latex"] let widget = IPythonWidget uuid stateIO
initData = object ["model_name" .= str "WidgetModel", "widget_class" .= str "IPython.Latex"]
-- Open a comm for this widget, and store it in the kernel state -- Open a comm for this widget, and store it in the kernel state
widgetSendOpen b initData (toJSON b) widgetSendOpen widget initData $ toJSON widgetState
-- Return the string widget -- Return the widget
return b return widget
-- | Set the Latex string value.
setLatexValue :: LatexWidget -> Text -> IO ()
setLatexValue b txt = do
modify b value txt
update b ["value" .= txt]
-- | Set the Latex description
setLatexDescription :: LatexWidget -> Text -> IO ()
setLatexDescription b txt = do
modify b description txt
update b ["description" .= txt]
-- | Set the Latex placeholder, i.e. text displayed in empty widget
setLatexPlaceholder :: LatexWidget -> Text -> IO ()
setLatexPlaceholder b txt = do
modify b placeholder txt
update b ["placeholder" .= txt]
-- | Set the Latex widget width.
setLatexWidth :: LatexWidget -> Int -> IO ()
setLatexWidth b wid = do
modify b width wid
update b ["width" .= wid]
-- | Get the Latex string value.
getLatexValue :: LatexWidget -> IO Text
getLatexValue = readIORef . value
-- | Get the Latex description value.
getLatexDescription :: LatexWidget -> IO Text
getLatexDescription = readIORef . description
-- | Get the Latex placeholder value.
getLatexPlaceholder :: LatexWidget -> IO Text
getLatexPlaceholder = readIORef . placeholder
-- | Get the Latex widget width.
getLatexWidth :: LatexWidget -> IO Int
getLatexWidth = readIORef . width
instance ToJSON LatexWidget where
toJSON b = object
[ "_view_name" .= str "LatexView"
, "visible" .= True
, "_css" .= object []
, "msg_throttle" .= (3 :: Int)
, "value" .= get value b
]
where
get x y = unsafePerformIO . readIORef . x $ y
instance IHaskellDisplay LatexWidget where instance IHaskellDisplay LatexWidget where
display b = do display b = do
......
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeSynonymInstances #-}
module IHaskell.Display.Widgets.String.Text ( module IHaskell.Display.Widgets.String.Text (
-- * The Text Widget -- * The Text Widget
TextWidget, TextWidget,
-- * Constructor -- * Constructor
mkTextWidget, mkTextWidget,
-- * Set properties -- * Submit handling
setTextValue, triggerSubmit) where
setTextDescription,
setTextPlaceholder,
-- * Get properties
getTextValue,
getTextDescription,
getTextPlaceholder,
-- * Submit handling
setSubmitHandler,
getSubmitHandler,
triggerSubmit,
) where
-- To keep `cabal repl` happy when running from the ihaskell repo -- To keep `cabal repl` happy when running from the ihaskell repo
import Prelude import Prelude
import Control.Monad (when, void) import Control.Monad (when, join)
import Data.Aeson (ToJSON, Value(..), object, toJSON, (.=)) import Data.Aeson
import Data.Aeson.Types (Pair) import qualified Data.HashMap.Strict as Map
import Data.HashMap.Strict as Map import Data.IORef (newIORef)
import Data.IORef
import Data.Text (Text) import Data.Text (Text)
import qualified Data.Text as T import Data.Vinyl (Rec(..), (<+>))
import System.IO.Unsafe (unsafePerformIO)
import IHaskell.Display import IHaskell.Display
import IHaskell.Eval.Widgets import IHaskell.Eval.Widgets
import qualified IHaskell.IPython.Message.UUID as U import IHaskell.IPython.Message.UUID as U
import IHaskell.Display.Widgets.Types
import IHaskell.Display.Widgets.Common import IHaskell.Display.Widgets.Common
data TextWidget = -- | A 'TextWidget' represents a Text widget from IPython.html.widgets.
TextWidget type TextWidget = IPythonWidget TextType
{ uuid :: U.UUID
, value :: IORef Text
, description :: IORef Text
, placeholder :: IORef Text
, submitHandler :: IORef (TextWidget -> IO ())
}
-- | Create a new Text widget -- | Create a new Text widget
mkTextWidget :: IO TextWidget mkTextWidget :: IO TextWidget
mkTextWidget = do mkTextWidget = do
-- Default properties, with a random uuid -- Default properties, with a random uuid
commUUID <- U.random uuid <- U.random
val <- newIORef "" let strWidget = defaultStringWidget "TextView"
des <- newIORef "" txtWidget = (SSubmitHandler =:: return ()) :& RNil
plc <- newIORef "" widgetState = WidgetState $ strWidget <+> txtWidget
sh <- newIORef $ const $ return ()
let b = TextWidget stateIO <- newIORef widgetState
{ uuid = commUUID
, value = val
, description = des
, placeholder = plc
, submitHandler = sh
}
let initData = object ["model_name" .= str "WidgetModel", "widget_class" .= str "IPython.Text"] let widget = IPythonWidget uuid stateIO
initData = object ["model_name" .= str "WidgetModel", "widget_class" .= str "IPython.Text"]
-- Open a comm for this widget, and store it in the kernel state -- Open a comm for this widget, and store it in the kernel state
widgetSendOpen b initData (toJSON b) widgetSendOpen widget initData $ toJSON widgetState
-- Return the string widget -- Return the widget
return b return widget
-- | Set the Text string value.
setTextValue :: TextWidget -> Text -> IO ()
setTextValue b txt = do
modify b value txt
update b ["value" .= txt]
-- | Set the text widget "description"
setTextDescription :: TextWidget -> Text -> IO ()
setTextDescription b txt = do
modify b description txt
update b ["description" .= txt]
-- | Set the text widget "placeholder", i.e. text displayed in empty text widget
setTextPlaceholder :: TextWidget -> Text -> IO ()
setTextPlaceholder b txt = do
modify b placeholder txt
update b ["placeholder" .= txt]
-- | Get the Text string value.
getTextValue :: TextWidget -> IO Text
getTextValue = readIORef . value
-- | Get the Text widget "description" value.
getTextDescription :: TextWidget -> IO Text
getTextDescription = readIORef . description
-- | Get the Text widget placeholder value.
getTextPlaceholder :: TextWidget -> IO Text
getTextPlaceholder = readIORef . placeholder
-- | Set a function to be activated on click
setSubmitHandler :: TextWidget -> (TextWidget -> IO ()) -> IO ()
setSubmitHandler = writeIORef . submitHandler
-- | Get the submit handler for a TextWidget
getSubmitHandler :: TextWidget -> IO (TextWidget -> IO ())
getSubmitHandler = readIORef . submitHandler
-- | Artificially trigger a TextWidget submit
triggerSubmit :: TextWidget -> IO () triggerSubmit :: TextWidget -> IO ()
triggerSubmit tw = do triggerSubmit tw = join $ getField tw SSubmitHandler
handler <- getSubmitHandler tw
handler tw
instance ToJSON TextWidget where
toJSON b = object
[ "_view_name" .= str "TextView"
, "visible" .= True
, "_css" .= object []
, "msg_throttle" .= (3 :: Int)
, "value" .= get value b
, "description" .= get description b
, "placeholder" .= get placeholder b
]
where
get x y = unsafePerformIO . readIORef . x $ y
instance IHaskellDisplay TextWidget where instance IHaskellDisplay TextWidget where
display b = do display b = do
...@@ -141,7 +66,7 @@ instance IHaskellWidget TextWidget where ...@@ -141,7 +66,7 @@ instance IHaskellWidget TextWidget where
case Map.lookup "sync_data" dict1 of case Map.lookup "sync_data" dict1 of
Just (Object dict2) -> Just (Object dict2) ->
case Map.lookup "value" dict2 of case Map.lookup "value" dict2 of
Just (String val) -> setTextValue tw val Just (String val) -> setField' tw SStringValue val
Nothing -> return () Nothing -> return ()
Nothing -> Nothing ->
case Map.lookup "content" dict1 of case Map.lookup "content" dict1 of
......
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeSynonymInstances #-}
module IHaskell.Display.Widgets.String.TextArea ( module IHaskell.Display.Widgets.String.TextArea (
-- * The TextArea Widget -- * The TextArea Widget
TextAreaWidget, TextAreaWidget,
-- * Constructor -- * Constructor
mkTextAreaWidget, mkTextAreaWidget) where
-- * Set properties
setTextAreaValue,
setTextAreaDescription,
setTextAreaPlaceholder,
-- * Get properties
getTextAreaValue,
getTextAreaDescription,
getTextAreaPlaceholder,
) where
-- To keep `cabal repl` happy when running from the ihaskell repo -- To keep `cabal repl` happy when running from the ihaskell repo
import Prelude import Prelude
import Control.Monad (when) import Control.Monad (when, join)
import Data.Aeson (ToJSON, Value(..), object, toJSON, (.=)) import Data.Aeson
import Data.Aeson.Types (Pair) import Data.IORef (newIORef)
import Data.HashMap.Strict as Map
import Data.IORef
import Data.Text (Text) import Data.Text (Text)
import qualified Data.Text as T import Data.Vinyl (Rec(..), (<+>))
import System.IO.Unsafe (unsafePerformIO)
import IHaskell.Display import IHaskell.Display
import IHaskell.Eval.Widgets import IHaskell.Eval.Widgets
import qualified IHaskell.IPython.Message.UUID as U import IHaskell.IPython.Message.UUID as U
import IHaskell.Display.Widgets.Common import IHaskell.Display.Widgets.Types
data TextAreaWidget = -- | A 'TextAreaWidget' represents a Textarea widget from IPython.html.widgets.
TextAreaWidget type TextAreaWidget = IPythonWidget TextAreaType
{ uuid :: U.UUID
, value :: IORef Text
, description :: IORef Text
, placeholder :: IORef Text
}
-- | Create a new TextArea widget -- | Create a new TextArea widget
mkTextAreaWidget :: IO TextAreaWidget mkTextAreaWidget :: IO TextAreaWidget
mkTextAreaWidget = do mkTextAreaWidget = do
-- Default properties, with a random uuid -- Default properties, with a random uuid
commUUID <- U.random uuid <- U.random
val <- newIORef "" let widgetState = WidgetState $ defaultStringWidget "TextareaView"
des <- newIORef ""
plc <- newIORef ""
let b = TextAreaWidget { uuid = commUUID, value = val, description = des, placeholder = plc } stateIO <- newIORef widgetState
let initData = object let widget = IPythonWidget uuid stateIO
initData = object
["model_name" .= str "WidgetModel", "widget_class" .= str "IPython.Textarea"] ["model_name" .= str "WidgetModel", "widget_class" .= str "IPython.Textarea"]
-- Open a comm for this widget, and store it in the kernel state -- Open a comm for this widget, and store it in the kernel state
widgetSendOpen b initData (toJSON b) widgetSendOpen widget initData $ toJSON widgetState
-- Return the string widget -- Return the widget
return b return widget
-- | Set the TextArea string value.
setTextAreaValue :: TextAreaWidget -> Text -> IO ()
setTextAreaValue b txt = do
modify b value txt
update b ["value" .= txt]
-- | Set the TextArea widget "description"
setTextAreaDescription :: TextAreaWidget -> Text -> IO ()
setTextAreaDescription b txt = do
modify b description txt
update b ["description" .= txt]
-- | Set the TextArea widget "placeholder", i.e. text displayed in empty widget
setTextAreaPlaceholder :: TextAreaWidget -> Text -> IO ()
setTextAreaPlaceholder b txt = do
modify b placeholder txt
update b ["placeholder" .= txt]
-- | Get the TextArea string value.
getTextAreaValue :: TextAreaWidget -> IO Text
getTextAreaValue = readIORef . value
-- | Get the TextArea widget "description" value.
getTextAreaDescription :: TextAreaWidget -> IO Text
getTextAreaDescription = readIORef . description
-- | Get the TextArea widget placeholder value.
getTextAreaPlaceholder :: TextAreaWidget -> IO Text
getTextAreaPlaceholder = readIORef . placeholder
instance ToJSON TextAreaWidget where
toJSON b = object
[ "_view_name" .= str "TextareaView"
, "visible" .= True
, "_css" .= object []
, "msg_throttle" .= (3 :: Int)
, "value" .= get value b
, "description" .= get description b
, "placeholder" .= get placeholder b
]
where
get x y = unsafePerformIO . readIORef . x $ y
instance IHaskellDisplay TextAreaWidget where instance IHaskellDisplay TextAreaWidget where
display b = do display b = do
......
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeSynonymInstances #-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE ConstraintKinds #-}
module IHaskell.Display.Widgets.Types where
-- | This module houses all the type-trickery needed to make widgets happen.
--
-- All widgets have a corresponding 'WidgetType', and some fields/attributes/properties as defined by
-- the 'WidgetFields' type-family.
--
-- Each widget field corresponds to a concrete haskell type, as given by the 'FieldType' type-family.
--
-- Vinyl records are used to wrap together widget fields into a single 'WidgetState'.
--
-- Singletons are used as a way to represent the promoted types of kind Field. For example:
--
-- @
-- SViewName :: SField ViewName
-- @
--
-- This allows the user to pass the type 'ViewName' without using Data.Proxy. In essence, a singleton
-- is the only inhabitant (other than bottom) of a promoted type. Single element set/type == singleton.
--
-- It also allows the record to wrap values of properties with information about their Field type. A
-- vinyl record is represented as @Rec f ts@, which means that a record is a list of @f x@, where @x@
-- is a type present in the type-level list @ts@. Thus a 'WidgetState' is essentially a list of field
-- properties wrapped together with the corresponding promoted Field type. See ('=::') for more.
--
-- The IPython widgets expect state updates of the form {"property": value}, where an empty string for
-- numeric values is ignored by the frontend and the default value is used instead.
--
-- To know more about the IPython messaging specification (as implemented in this package) take a look
-- at the supplied MsgSpec.md.
import Control.Monad (when)
import Control.Applicative ((<$>))
import Data.Aeson
import Data.Aeson.Types (emptyObject, Pair)
import Data.Text (pack, Text)
import Data.IORef (IORef, readIORef, modifyIORef)
import Data.Vinyl (Rec (..), (<+>), recordToList, reifyConstraint, rmap, Dict (..))
import Data.Vinyl.Functor (Compose (..), Const (..))
import Data.Vinyl.Lens (rget, rput, type ())
import Data.Vinyl.TypeLevel (RecAll (..))
import Data.Singletons.Prelude ((:++))
import Data.Singletons.TH
import Numeric.Natural
import IHaskell.Eval.Widgets (widgetSendUpdate)
import IHaskell.Display (Base64, IHaskellWidget (..))
import IHaskell.IPython.Message.UUID
import IHaskell.Display.Widgets.Common
-- Classes from IPython's widget hierarchy. Defined as such to reduce code duplication.
type WidgetClass = '[ModelModule, ModelName, ViewModule, ViewName, MsgThrottle, Version, OnDisplayed]
type DOMWidgetClass = WidgetClass :++
'[ Visible, CSS, DOMClasses, Width, Height, Padding, Margin, Color
, BackgroundColor, BorderColor, BorderWidth, BorderRadius, BorderStyle, FontStyle
, FontWeight, FontSize, FontFamily
]
type StringClass = DOMWidgetClass :++ '[StringValue, Disabled, Description, Placeholder]
type BoolClass = DOMWidgetClass :++ '[BoolValue, Disabled, Description]
type SelectionClass = DOMWidgetClass :++
'[Options, SelectedValue, SelectedLabel, Disabled, Description, SelectionHandler]
type MultipleSelectionClass = DOMWidgetClass :++
'[Options, SelectedLabels, SelectedValues, Disabled, Description, SelectionHandler]
-- Types associated with Fields.
type family FieldType (f :: Field) :: * where
FieldType ModelModule = Text
FieldType ModelName = Text
FieldType ViewModule = Text
FieldType ViewName = Text
FieldType MsgThrottle = Natural
FieldType Version = Natural
FieldType OnDisplayed = IO ()
FieldType Visible = Bool
FieldType CSS = [(Text, Text, Text)]
FieldType DOMClasses = [Text]
FieldType Width = Natural
FieldType Height = Natural
FieldType Padding = Natural
FieldType Margin = Natural
FieldType Color = Text
FieldType BackgroundColor = Text
FieldType BorderColor = Text
FieldType BorderWidth = Natural
FieldType BorderRadius = Natural
FieldType BorderStyle = BorderStyleValue
FieldType FontStyle = FontStyleValue
FieldType FontWeight = FontWeightValue
FieldType FontSize = Natural
FieldType FontFamily = Text
FieldType Description = Text
FieldType ClickHandler = IO ()
FieldType SubmitHandler = IO ()
FieldType Disabled = Bool
FieldType StringValue = Text
FieldType Placeholder = Text
FieldType Tooltip = Text
FieldType Icon = Text
FieldType ButtonStyle = ButtonStyleValue
FieldType B64Value = Base64
FieldType ImageFormat = ImageFormatValue
FieldType BoolValue = Bool
FieldType Options = SelectionOptions
FieldType SelectedLabel = Text
FieldType SelectedValue = Text
FieldType SelectionHandler = IO ()
FieldType Tooltips = [Text]
FieldType Icons = [Text]
FieldType SelectedLabels = [Text]
FieldType SelectedValues = [Text]
-- Different types of widgets. Every widget in IPython has a corresponding WidgetType
data WidgetType = ButtonType
| ImageType
| OutputType
| HTMLType
| LatexType
| TextType
| TextAreaType
| CheckBoxType
| ToggleButtonType
| DropdownType
| RadioButtonsType
| SelectType
| ToggleButtonsType
| SelectMultipleType
-- Fields associated with a widget
type family WidgetFields (w :: WidgetType) :: [Field] where
WidgetFields ButtonType = DOMWidgetClass :++ '[Description, Tooltip, Disabled, Icon, ButtonStyle, ClickHandler]
WidgetFields ImageType = DOMWidgetClass :++ '[ImageFormat, B64Value]
WidgetFields OutputType = DOMWidgetClass
WidgetFields HTMLType = StringClass
WidgetFields LatexType = StringClass
WidgetFields TextType = StringClass :++ '[SubmitHandler]
WidgetFields TextAreaType = StringClass
WidgetFields CheckBoxType = BoolClass
WidgetFields ToggleButtonType = BoolClass :++ '[Tooltip, Icon, ButtonStyle]
WidgetFields DropdownType = SelectionClass :++ '[ButtonStyle]
WidgetFields RadioButtonsType = SelectionClass
WidgetFields SelectType = SelectionClass
WidgetFields ToggleButtonsType = SelectionClass :++ '[Tooltips, Icons, ButtonStyle]
WidgetFields SelectMultipleType = MultipleSelectionClass
-- Wrapper around a field
newtype Attr (f :: Field) = Attr { _unAttr :: FieldType f }
-- Types that can be converted to Aeson Pairs.
class ToPairs a where
toPairs :: a -> [Pair]
-- Attributes that aren't synced with the frontend give [] on toPairs
instance ToPairs (Attr ModelModule) where toPairs (Attr x) = ["_model_module" .= toJSON x]
instance ToPairs (Attr ModelName) where toPairs (Attr x) = ["_model_name" .= toJSON x]
instance ToPairs (Attr ViewModule) where toPairs (Attr x) = ["_view_module" .= toJSON x]
instance ToPairs (Attr ViewName) where toPairs (Attr x) = ["_view_name" .= toJSON x]
instance ToPairs (Attr MsgThrottle) where toPairs (Attr x) = ["msg_throttle" .= toJSON x]
instance ToPairs (Attr Version) where toPairs (Attr x) = ["version" .= toJSON x]
instance ToPairs (Attr OnDisplayed) where toPairs _ = [] -- Not sent to the frontend
instance ToPairs (Attr Visible) where toPairs (Attr x) = ["visible" .= toJSON x]
instance ToPairs (Attr CSS) where toPairs (Attr x) = ["_css" .= toJSON x]
instance ToPairs (Attr DOMClasses) where toPairs (Attr x) = ["_dom_classes" .= toJSON x]
instance ToPairs (Attr Width) where toPairs (Attr x) = ["width" .= toJSON x]
instance ToPairs (Attr Height) where toPairs (Attr x) = ["height" .= toJSON x]
instance ToPairs (Attr Padding) where toPairs (Attr x) = ["padding" .= toJSON x]
instance ToPairs (Attr Margin) where toPairs (Attr x) = ["margin" .= toJSON x]
instance ToPairs (Attr Color) where toPairs (Attr x) = ["color" .= toJSON x]
instance ToPairs (Attr BackgroundColor) where toPairs (Attr x) = ["background_color" .= toJSON x]
instance ToPairs (Attr BorderColor) where toPairs (Attr x) = ["border_color" .= toJSON x]
instance ToPairs (Attr BorderWidth) where toPairs (Attr x) = ["border_width" .= toJSON x]
instance ToPairs (Attr BorderRadius) where toPairs (Attr x) = ["border_radius" .= toJSON x]
instance ToPairs (Attr BorderStyle) where toPairs (Attr x) = ["border_style" .= toJSON x]
instance ToPairs (Attr FontStyle) where toPairs (Attr x) = ["font_style" .= toJSON x]
instance ToPairs (Attr FontWeight) where toPairs (Attr x) = ["font_weight" .= toJSON x]
instance ToPairs (Attr FontSize) where toPairs (Attr x) = ["font_size" .= toJSON x]
instance ToPairs (Attr FontFamily) where toPairs (Attr x) = ["font_family" .= toJSON x]
instance ToPairs (Attr Description) where toPairs (Attr x) = ["description" .= toJSON x]
instance ToPairs (Attr ClickHandler) where toPairs _ = [] -- Not sent to the frontend
instance ToPairs (Attr SubmitHandler) where toPairs _ = [] -- Not sent to the frontend
instance ToPairs (Attr Disabled) where toPairs (Attr x) = ["disabled" .= toJSON x]
instance ToPairs (Attr StringValue) where toPairs (Attr x) = ["value" .= toJSON x]
instance ToPairs (Attr Placeholder) where toPairs (Attr x) = ["placeholder" .= toJSON x]
instance ToPairs (Attr Tooltip) where toPairs (Attr x) = ["tooltip" .= toJSON x]
instance ToPairs (Attr Icon) where toPairs (Attr x) = ["icon" .= toJSON x]
instance ToPairs (Attr ButtonStyle) where toPairs (Attr x) = ["button_style" .= toJSON x]
instance ToPairs (Attr B64Value) where toPairs (Attr x) = ["_b64value" .= toJSON x]
instance ToPairs (Attr ImageFormat) where toPairs (Attr x) = ["format" .= toJSON x]
instance ToPairs (Attr BoolValue) where toPairs (Attr x) = ["value" .= toJSON x]
instance ToPairs (Attr SelectedLabel) where toPairs (Attr x) = ["selected_label" .= toJSON x]
instance ToPairs (Attr SelectedValue) where toPairs (Attr x) = ["value" .= toJSON x]
instance ToPairs (Attr Options) where
toPairs (Attr x) = case x of
OptionLabels xs -> labels xs
OptionDict xps -> labels $ map fst xps
where labels xs = ["_options_labels" .= xs]
instance ToPairs (Attr SelectionHandler) where toPairs _ = [] -- Not sent to the frontend
instance ToPairs (Attr Tooltips) where toPairs (Attr x) = ["tooltips" .= toJSON x]
instance ToPairs (Attr Icons) where toPairs (Attr x) = ["icons" .= toJSON x]
instance ToPairs (Attr SelectedLabels) where toPairs (Attr x) = ["selected_labels" .= toJSON x]
instance ToPairs (Attr SelectedValues) where toPairs (Attr x) = ["values" .= toJSON x]
-- | Store the value for a field, as an object parametrized by the Field
(=::) :: sing f -> FieldType f -> Attr f
_ =:: x = Attr x
-- | A record representing an object of the Widget class from IPython
defaultWidget :: FieldType ViewName -> Rec Attr WidgetClass
defaultWidget viewName = (SModelModule =:: "")
:& (SModelName =:: "WidgetModel")
:& (SViewModule =:: "")
:& (SViewName =:: viewName)
:& (SMsgThrottle =:: 3)
:& (SVersion =:: 0)
:& (SOnDisplayed =:: return ())
:& RNil
-- | A record representing an object of the DOMWidget class from IPython
defaultDOMWidget :: FieldType ViewName -> Rec Attr DOMWidgetClass
defaultDOMWidget viewName = defaultWidget viewName <+> domAttrs
where domAttrs = (SVisible =:: True)
:& (SCSS =:: [])
:& (SDOMClasses =:: [])
:& (SWidth =:: 0)
:& (SHeight =:: 0)
:& (SPadding =:: 0)
:& (SMargin =:: 0)
:& (SColor =:: "")
:& (SBackgroundColor =:: "")
:& (SBorderColor =:: "")
:& (SBorderWidth =:: 0)
:& (SBorderRadius =:: 0)
:& (SBorderStyle =:: DefaultBorder)
:& (SFontStyle =:: DefaultFont)
:& (SFontWeight =:: DefaultWeight)
:& (SFontSize =:: 0)
:& (SFontFamily =:: "")
:& RNil
-- | A record representing a widget of the _String class from IPython
defaultStringWidget :: FieldType ViewName -> Rec Attr StringClass
defaultStringWidget viewName = defaultDOMWidget viewName <+> strAttrs
where strAttrs = (SStringValue =:: "")
:& (SDisabled =:: False)
:& (SDescription =:: "")
:& (SPlaceholder =:: "")
:& RNil
-- | A record representing a widget of the _Bool class from IPython
defaultBoolWidget :: FieldType ViewName -> Rec Attr BoolClass
defaultBoolWidget viewName = defaultDOMWidget viewName <+> boolAttrs
where boolAttrs = (SBoolValue =:: False)
:& (SDisabled =:: False)
:& (SDescription =:: "")
:& RNil
-- | A record representing a widget of the _Selection class from IPython
defaultSelectionWidget :: FieldType ViewName -> Rec Attr SelectionClass
defaultSelectionWidget viewName = defaultDOMWidget viewName <+> selectionAttrs
where selectionAttrs = (SOptions =:: OptionLabels [])
:& (SSelectedValue =:: "")
:& (SSelectedLabel =:: "")
:& (SDisabled =:: False)
:& (SDescription =:: "")
:& (SSelectionHandler =:: return ())
:& RNil
-- | A record representing a widget of the _MultipleSelection class from IPython
defaultMultipleSelectionWidget :: FieldType ViewName -> Rec Attr MultipleSelectionClass
defaultMultipleSelectionWidget viewName = defaultDOMWidget viewName <+> mulSelAttrs
where mulSelAttrs = (SOptions =:: OptionLabels [])
:& (SSelectedLabels =:: [])
:& (SSelectedValues =:: [])
:& (SDisabled =:: False)
:& (SDescription =:: "")
:& (SSelectionHandler =:: return ())
:& RNil
newtype WidgetState w = WidgetState { _getState :: Rec Attr (WidgetFields w) }
-- All records with ToPair instances for their Attrs will automatically have a toJSON instance now.
instance RecAll Attr (WidgetFields w) ToPairs => ToJSON (WidgetState w) where
toJSON record =
object
. concat
. recordToList
. rmap (\(Compose (Dict x)) -> Const $ toPairs x)
$ reifyConstraint (Proxy :: Proxy ToPairs) $ _getState record
data IPythonWidget (w :: WidgetType) = IPythonWidget { uuid :: UUID, state :: IORef (WidgetState w) }
-- | Change the value for a field, and notify the frontend about it.
setField :: (f WidgetFields w, IHaskellWidget (IPythonWidget w), ToPairs (Attr f)) => IPythonWidget w -> SField f -> FieldType f -> IO ()
setField widget (sfield :: SField f) fval = do
setField' widget sfield fval
let pairs = toPairs (Attr fval :: Attr f)
when (not . null $ pairs) $ widgetSendUpdate widget (object pairs)
-- | Change the value of a field, without notifying the frontend. For internal use. Uses BangPattern.
setField' :: (f WidgetFields w, IHaskellWidget (IPythonWidget w)) => IPythonWidget w -> SField f -> FieldType f -> IO ()
setField' widget sfield !fval = modifyIORef (state widget) (WidgetState . rput (sfield =:: fval) . _getState)
-- | Get the value of a field.
getField :: (f WidgetFields w) => IPythonWidget w -> SField f -> IO (FieldType f)
getField widget sfield = _unAttr <$> rget sfield <$> _getState <$> readIORef (state widget)
-- | Useful with toJSON and OverloadedStrings
str :: String -> String
str = id
-- | Send zero values as empty strings, which stands for default value in the frontend.
instance ToJSON Natural where
toJSON 0 = String ""
toJSON n = String . pack $ show n
...@@ -239,6 +239,10 @@ initializeImports = do ...@@ -239,6 +239,10 @@ initializeImports = do
imports <- mapM parseImportDecl $ globalImports ++ displayImports imports <- mapM parseImportDecl $ globalImports ++ displayImports
setContext $ map IIDecl $ implicitPrelude : imports setContext $ map IIDecl $ implicitPrelude : imports
-- Set -fcontext-stack to 100 (default in ghc-7.10). ghc-7.8 uses 20, which is too small.
let contextStackFlag = printf "-fcontext-stack=%d" (100 :: Int)
void $ setFlags [contextStackFlag]
-- | Give a value for the `it` variable. -- | Give a value for the `it` variable.
initializeItVariable :: Interpreter () initializeItVariable :: Interpreter ()
initializeItVariable = initializeItVariable =
......
...@@ -51,9 +51,14 @@ for source_dir in ["src", "ipython-kernel", "ihaskell-display"]: ...@@ -51,9 +51,14 @@ for source_dir in ["src", "ipython-kernel", "ihaskell-display"]:
continue continue
for filename in filenames: for filename in filenames:
# Take Haskell files, but ignore the Cabal Setup.hs if "ihaskell-display/ihaskell-widgets/src/IHaskell/Display/Widgets" in root:
# Also ignore IHaskellPrelude.hs, it uses CPP in weird places # Ignore Types.hs and Common.hs from ihaskell-widgets
ignored_files = ["Setup.hs", "IHaskellPrelude.hs"] # They cause issues with hindent, due to promoted types
ignored_files = ["Types.hs", "Common.hs"]
else:
# Take Haskell files, but ignore the Cabal Setup.hs
# Also ignore IHaskellPrelude.hs, it uses CPP in weird places
ignored_files = ["Setup.hs", "IHaskellPrelude.hs"]
if filename.endswith(".hs") and filename not in ignored_files: if filename.endswith(".hs") and filename not in ignored_files:
sources.append(os.path.join(root, filename)) sources.append(os.path.join(root, filename))
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment