diff --git a/ihaskell-display/ihaskell-widgets/src/IHaskell/Display/Widgets/Interactive.hs b/ihaskell-display/ihaskell-widgets/src/IHaskell/Display/Widgets/Interactive.hs index b759780cd465c9fe9697eeb6bedad1a4104b1e2d..aa6f52bd8771c314f2be386a8f61badf2e44e31c 100644 --- a/ihaskell-display/ihaskell-widgets/src/IHaskell/Display/Widgets/Interactive.hs +++ b/ihaskell-display/ihaskell-widgets/src/IHaskell/Display/Widgets/Interactive.hs @@ -5,14 +5,23 @@ {-# LANGUAGE GADTs #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE PolyKinds #-} +{-# LANGUAGE KindSignatures #-} +{-# LANGUAGE ScopedTypeVariables #-} -module IHaskell.Display.Widgets.Interactive (interactive) where +module IHaskell.Display.Widgets.Interactive (interactive, usingHList) where import Data.Text +import Data.Proxy +import Data.Vinyl.Core +import Data.Vinyl.Functor (Identity (..), Const (..)) +import Data.Vinyl.Derived (HList) import Data.Vinyl.Lens (type (∈)) import Data.Vinyl.TypeLevel (RecAll) +import Data.Singletons.Prelude.List + import IHaskell.Display import IHaskell.Display.Widgets.Types @@ -24,14 +33,119 @@ import IHaskell.Display.Widgets.String.Text import IHaskell.Display.Widgets.Int.BoundedInt.IntSlider import IHaskell.Display.Widgets.Output - +data WidgetConf a where + WidgetConf :: RecAll Attr (WidgetFields (SuitableWidget a)) ToPairs + => WrappedWidget (SuitableWidget a) (SuitableHandler a) (SuitableField a) a + -> WidgetConf a + +newtype WrappedConstructor a = WrappedConstructor { + wrappedConstructor :: IO (IPythonWidget (SuitableWidget a)) +} + +type family WithTypes (ts :: [*]) (r :: *) :: * where + WithTypes '[] r = r + WithTypes (x ': xs) r = (x -> WithTypes xs r) + +-- | Convert a function to one accepting arguments in the form of an HList +usingHList :: WithTypes ts r -> HList ts -> r +usingHList f RNil = f +usingHList f (Identity x :& xs) = usingHList (f x) xs + +-- Phantom 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 Setter a = Setter (IPythonWidget (SuitableWidget a) -> IO () -> IO ()) +newtype Trigger a = Trigger (IPythonWidget (SuitableWidget 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 + +applySetters :: Rec Setter ts -> Rec RequiredWidget ts -> IO () -> IO () +applySetters RNil RNil _ = return () +applySetters (Setter setter :& xs) (RequiredWidget widget :& ws) handler = do + setter widget handler + applySetters xs ws handler + +extractConstructor :: WidgetConf x -> Constructor x +extractConstructor (WidgetConf wr) = Constructor $ construct wr + +extractGetter :: WidgetConf x -> Getter x +extractGetter (WidgetConf wr) = Getter $ getValue wr + +extractSetter :: WidgetConf x -> Setter x +extractSetter (WidgetConf wr) = Setter $ setEvent wr + +extractTrigger :: WidgetConf x -> Trigger x +extractTrigger (WidgetConf wr) = Trigger $ trigger wr + +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 + +{- +-- TODO: Finish This + +mkConfs SNil = RNil +mkConfs (SCons selem srest) = WidgetConf wrapped :& mkConfs srest + +interactive :: (RecAll Identity ts FromWidget, IHaskellDisplay r) + => (HList ts -> r) -> IO FlexBox +interactive func = let confs = mkConfs undefined + in liftToWidgets func confs +-} +interactive = undefined + +-- | Lift a function (HList ts -> r) to one using widgets to fill the HList and displaying the +-- output through the resultant widget. +liftToWidgets :: (RecAll Identity ts FromWidget, IHaskellDisplay r) + => (HList ts -> r) -> Rec WidgetConf ts -> IO FlexBox +liftToWidgets func rc = do + let constructors = rmap extractConstructor rc + getters = rmap extractGetter rc + setters = rmap extractSetter rc + triggers = rmap extractTrigger rc + + bx <- mkFlexBox + 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 + applySetters setters widgets handler + + setField out Width 500 + setField bx Orientation VerticalOrientation + + -- Set children for the FlexBox + 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)) => - IO (IPythonWidget w) -> - S.SField h -> S.SField f -> WrappedWidget w h f a + WrappedWidget :: (FieldType h ~ IO (), FieldType f ~ a, h ∈ WidgetFields w, f ∈ WidgetFields w, + ToPairs (Attr h), IHaskellWidget (IPythonWidget w)) + => 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 cons _ _) = cons @@ -63,15 +177,15 @@ instance FromWidget Integer where type SuitableField Integer = S.IntValue wrapped = WrappedWidget mkIntSlider ChangeHandler IntValue -interactive :: (FromWidget a, IHaskellDisplay b) => (a -> b) -> IO FlexBox -interactive func = do - let wrap = wrapped - widget <- construct wrap - bx <- mkFlexBox - out <- mkOutputWidget - setEvent wrap widget $ getValue wrap widget >>= replaceOutput out . func - trigger wrap widget - setField out Width 500 - setField bx Orientation VerticalOrientation - setField bx Children [ChildWidget widget, ChildWidget out] - return bx +-- interactive :: (FromWidget a, IHaskellDisplay b) => (a -> b) -> IO FlexBox +-- interactive func = do +-- let wrap = wrapped +-- widget <- construct wrap +-- bx <- mkFlexBox +-- out <- mkOutputWidget +-- setEvent wrap widget $ getValue wrap widget >>= replaceOutput out . func +-- trigger wrap widget +-- setField out Width 500 +-- setField bx Orientation VerticalOrientation +-- setField bx Children [ChildWidget widget, ChildWidget out] +-- return bx