{-# LANGUAGE CPP #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE UndecidableSuperClasses #-} {-# LANGUAGE PolyKinds #-} module IHaskell.Display.Widgets.Interactive ( interactive , uncurryHList , Rec (..) , Argument(..) ) where import Data.Text import Data.Proxy #if MIN_VERSION_vinyl(0,9,0) import Data.Vinyl.Core (Rec(..)) import Data.Vinyl.Recursive (recordToList, rmap, rtraverse) #else import Data.Vinyl.Core (Rec(..), recordToList, rmap, rtraverse) #endif import Data.Vinyl.Functor (Identity(..), Const(..)) import Data.Vinyl.Derived (HList) import Data.Vinyl.Lens (type (∈)) import Data.Vinyl.TypeLevel (RecAll) import IHaskell.Display import IHaskell.Display.Widgets.Types import IHaskell.Display.Widgets.Common import qualified IHaskell.Display.Widgets.Singletons as S (SField, Field(..)) import IHaskell.Display.Widgets.Box.Box import IHaskell.Display.Widgets.Bool.CheckBox import IHaskell.Display.Widgets.String.Text import IHaskell.Display.Widgets.Int.BoundedInt.IntSlider import IHaskell.Display.Widgets.Float.BoundedFloat.FloatSlider import IHaskell.Display.Widgets.Output data WidgetConf a where WidgetConf :: (RecAll Attr (WidgetFields (SuitableWidget a)) ToPairs, FromWidget a) => WrappedWidget (SuitableWidget a) (SuitableHandler a) (SuitableField a) a -> WidgetConf a type family WithTypes (ts :: [*]) (r :: *) :: * where WithTypes '[] r = r WithTypes (x ': xs) r = (x -> WithTypes xs r) uncurryHList :: WithTypes ts r -> HList ts -> r uncurryHList f RNil = f uncurryHList f (Identity x :& xs) = uncurryHList (f x) xs -- Consistent type variables are required to make things play nicely with vinyl data Constructor a where Constructor :: RecAll Attr (WidgetFields (SuitableWidget a)) ToPairs => IO (IPythonWidget (SuitableWidget a)) -> Constructor a newtype Getter a = Getter (IPythonWidget (SuitableWidget a) -> IO a) newtype EventSetter a = EventSetter (IPythonWidget (SuitableWidget a) -> IO () -> IO ()) newtype Initializer a = Initializer (IPythonWidget (SuitableWidget a) -> Argument a -> IO ()) data RequiredWidget a where RequiredWidget :: RecAll Attr (WidgetFields (SuitableWidget a)) ToPairs => IPythonWidget (SuitableWidget a) -> RequiredWidget a -- Zipping vinyl records in various ways applyGetters :: Rec Getter ts -> Rec RequiredWidget ts -> IO (HList ts) applyGetters RNil RNil = return RNil applyGetters (Getter getter :& gs) (RequiredWidget widget :& ws) = do val <- getter widget rest <- applyGetters gs ws return $ Identity val :& rest applyEventSetters :: Rec EventSetter ts -> Rec RequiredWidget ts -> IO () -> IO () applyEventSetters RNil RNil _ = return () applyEventSetters (EventSetter setter :& xs) (RequiredWidget widget :& ws) handler = do setter widget handler applyEventSetters xs ws handler setInitialValues :: Rec Initializer ts -> Rec RequiredWidget ts -> Rec Argument ts -> IO () setInitialValues RNil RNil RNil = return () setInitialValues (Initializer initialize :& fs) (RequiredWidget widget :& ws) (argument :& vs) = do initialize widget argument setInitialValues fs ws vs extractConstructor :: WidgetConf x -> Constructor x extractConstructor (WidgetConf wr) = Constructor $ construct wr extractGetter :: WidgetConf x -> Getter x extractGetter (WidgetConf wr) = Getter $ getValue wr extractEventSetter :: WidgetConf x -> EventSetter x extractEventSetter (WidgetConf wr) = EventSetter $ setEvent wr extractInitializer :: WidgetConf x -> Initializer x extractInitializer WidgetConf{} = Initializer initializer createWidget :: Constructor a -> IO (RequiredWidget a) createWidget (Constructor con) = fmap RequiredWidget con mkChildren :: Rec RequiredWidget a -> [ChildWidget] mkChildren widgets = let childRecord = rmap (\(RequiredWidget w) -> Const (ChildWidget w)) widgets in recordToList childRecord class MakeConfs (ts :: [*]) where mkConfs :: proxy ts -> Rec WidgetConf ts instance MakeConfs '[] where mkConfs _ = RNil instance (FromWidget t, MakeConfs ts) => MakeConfs (t ': ts) where mkConfs _ = WidgetConf wrapped :& mkConfs (Proxy :: Proxy ts) interactive :: (IHaskellDisplay r, MakeConfs ts) => (HList ts -> r) -> Rec Argument ts -> IO Box interactive func = let confs = mkConfs Proxy in liftToWidgets func confs -- | Transform a function (HList ts -> r) to one which: 1) Uses widgets to accept the arguments 2) -- Accepts initial values for the arguments 3) Creates a compound Box widget with an embedded -- OutputWidget for display liftToWidgets :: IHaskellDisplay r => (HList ts -> r) -> Rec WidgetConf ts -> Rec Argument ts -> IO Box liftToWidgets func rc initvals = do let constructors = rmap extractConstructor rc getters = rmap extractGetter rc eventSetters = rmap extractEventSetter rc initializers = rmap extractInitializer rc bx <- mkBox out <- mkOutputWidget -- Create a list of widgets widgets <- rtraverse createWidget constructors let handler = do vals <- applyGetters getters widgets replaceOutput out $ func vals -- Apply handler to all widgets applyEventSetters eventSetters widgets handler -- Set initial values for all widgets setInitialValues initializers widgets initvals -- applyValueSetters valueSetters widgets $ getList defvals setField out Width 500 -- TODO This can't be set right now since we switched FlexBox to a regular -- Box. This is a styling/layout parameter now but these haven't been implemented yet. -- setField bx Orientation VerticalOrientation -- Set children for the Box let children = mkChildren widgets setField bx Children $ children ++ [ChildWidget out] return bx data WrappedWidget w h f a where WrappedWidget :: (FieldType h ~ IO (), FieldType f ~ a, h ∈ WidgetFields w, f ∈ WidgetFields w, ToPairs (Attr h), IHaskellWidget (IPythonWidget w), ToPairs (Attr f)) => IO (IPythonWidget w) -> S.SField h -> S.SField f -> WrappedWidget w h f a construct :: WrappedWidget w h f a -> IO (IPythonWidget w) construct (WrappedWidget cs _ _) = cs getValue :: WrappedWidget w h f a -> IPythonWidget w -> IO a getValue (WrappedWidget _ _ field) widget = getField widget field setEvent :: WrappedWidget w h f a -> IPythonWidget w -> IO () -> IO () setEvent (WrappedWidget _ h _) widget = setField widget h class RecAll Attr (WidgetFields (SuitableWidget a)) ToPairs => FromWidget a where type SuitableWidget a :: WidgetType type SuitableHandler a :: S.Field type SuitableField a :: S.Field data Argument a initializer :: IPythonWidget (SuitableWidget a) -> Argument a -> IO () wrapped :: WrappedWidget (SuitableWidget a) (SuitableHandler a) (SuitableField a) a instance FromWidget Bool where type SuitableWidget Bool = 'CheckBoxType type SuitableHandler Bool = 'S.ChangeHandler type SuitableField Bool = 'S.BoolValue data Argument Bool = BoolVal Bool initializer w (BoolVal b) = setField w BoolValue b wrapped = WrappedWidget mkCheckBox ChangeHandler BoolValue instance FromWidget Text where type SuitableWidget Text = 'TextType type SuitableHandler Text = 'S.SubmitHandler type SuitableField Text = 'S.StringValue data Argument Text = TextVal Text initializer w (TextVal txt) = setField w StringValue txt wrapped = WrappedWidget mkTextWidget SubmitHandler StringValue instance FromWidget Integer where type SuitableWidget Integer = 'IntSliderType type SuitableHandler Integer = 'S.ChangeHandler type SuitableField Integer = 'S.IntValue data Argument Integer = IntVal Integer | IntRange (Integer, Integer, Integer) wrapped = WrappedWidget mkIntSlider ChangeHandler IntValue initializer w (IntVal int) = setField w IntValue int initializer w (IntRange (v, l, u)) = do setField w IntValue v setField w MinInt l setField w MaxInt u instance FromWidget Double where type SuitableWidget Double = 'FloatSliderType type SuitableHandler Double = 'S.ChangeHandler type SuitableField Double = 'S.FloatValue data Argument Double = FloatVal Double | FloatRange (Double, Double, Double) wrapped = WrappedWidget mkFloatSlider ChangeHandler FloatValue initializer w (FloatVal d) = setField w FloatValue d initializer w (FloatRange (v, l, u)) = do setField w FloatValue v setField w MinFloat l setField w MaxFloat u