Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,17 @@

## Unreleased

### Changed

- BREAKING: text color moved from `WidgetStyle` into the text-bearing
configs (`tcTextColor` / `bcTextColor` / `tiTextColor`, next to the
font override), following the `tcFontConfig` precedent. A text
color can no longer be attached to a container node, where it
silently changed nothing (#242); the new `coloredText` smart
constructor covers the common case. Keyframe animation of text
color went with the field (nothing used it; animate the background
color instead).

### Added

- Scan results now carry the advertisement's service data and
Expand Down
3 changes: 3 additions & 0 deletions src/Hatter.hs
Original file line number Diff line number Diff line change
Expand Up @@ -305,15 +305,18 @@ errorWidget dismissAction exc = column
[ Text TextConfig
{ tcLabel = "An error occurred"
, tcFontConfig = Just (FontConfig 20.0)
, tcTextColor = Nothing
}
, Text TextConfig
{ tcLabel = pack (show exc)
, tcFontConfig = Nothing
, tcTextColor = Nothing
}
, Button ButtonConfig
{ bcLabel = "Dismiss"
, bcAction = dismissAction
, bcFontConfig = Nothing
, bcTextColor = Nothing
}
]

Expand Down
3 changes: 0 additions & 3 deletions src/Hatter/Animation.hs
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,6 @@ defaultStyleEmpty :: WidgetStyle
defaultStyleEmpty = WidgetStyle
{ wsPadding = Nothing
, wsTextAlign = Nothing
, wsTextColor = Nothing
, wsBackgroundColor = Nothing
, wsTranslateX = Nothing
, wsTranslateY = Nothing
Expand Down Expand Up @@ -212,8 +211,6 @@ interpolateStyle :: Int32 -> WidgetStyle -> WidgetStyle -> Double -> IO ()
interpolateStyle nodeId fromStyle toStyle progress = do
lerpNumProp Bridge.PropPadding
(wsPadding fromStyle) (wsPadding toStyle)
lerpColorProp Bridge.PropColor
(wsTextColor fromStyle) (wsTextColor toStyle)
lerpColorProp Bridge.PropBgColor
(wsBackgroundColor fromStyle) (wsBackgroundColor toStyle)
lerpNumProp Bridge.PropTranslateX
Expand Down
24 changes: 20 additions & 4 deletions src/Hatter/Render.hs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import Data.List (sortBy)
import Data.Ord (comparing)
import Unwitch.Convert.Int32 qualified as Int32
import Hatter.Animation (AnimationState, registerTween)
import Hatter.Widget (AnimatedConfig(..), ButtonConfig(..), FontConfig(..), ImageConfig(..), ImageSource(..), InputType(..), Keyframe(..), LayoutItem(..), LayoutSettings(..), MapViewConfig(..), ResourceName(..), ScaleType(..), TextAlignment(..), TextConfig(..), TextInputConfig(..), WebViewConfig(..), Widget(..), WidgetStyle(..), colorToHex, normalizeAnimated, resolveKeyAtIndex)
import Hatter.Widget (AnimatedConfig(..), ButtonConfig(..), Color, FontConfig(..), ImageConfig(..), ImageSource(..), InputType(..), Keyframe(..), LayoutItem(..), LayoutSettings(..), MapViewConfig(..), ResourceName(..), ScaleType(..), TextAlignment(..), TextConfig(..), TextInputConfig(..), WebViewConfig(..), Widget(..), WidgetStyle(..), colorToHex, normalizeAnimated, resolveKeyAtIndex)

import Hatter.UIBridge qualified as Bridge
import System.IO (hPutStrLn, stderr)
Expand Down Expand Up @@ -132,6 +132,13 @@ applyFontConfig nodeId (Just (FontConfig size)) =
Bridge.setNumProp nodeId Bridge.PropFontSize size
applyFontConfig _nodeId Nothing = pure ()

-- | Apply a text color to a rendered node if present (the per-config
-- color, see 'tcTextColor').
applyTextColor :: Int32 -> Maybe Color -> IO ()
applyTextColor nodeId (Just color) =
Bridge.setStrProp nodeId Bridge.PropColor (colorToHex color)
applyTextColor _nodeId Nothing = pure ()

-- | Map a 'ScaleType' to the numeric code sent to the platform bridge.
scaleTypeToDouble :: ScaleType -> Double
scaleTypeToDouble ScaleFit = 0
Expand All @@ -154,9 +161,6 @@ applyStyle nodeId style = do
case wsTextAlign style of
Just alignment -> Bridge.setNumProp nodeId Bridge.PropGravity (textAlignToDouble alignment)
Nothing -> pure ()
case wsTextColor style of
Just color -> Bridge.setStrProp nodeId Bridge.PropColor (colorToHex color)
Nothing -> pure ()
case wsBackgroundColor style of
Just color -> Bridge.setStrProp nodeId Bridge.PropBgColor (colorToHex color)
Nothing -> pure ()
Expand Down Expand Up @@ -197,12 +201,14 @@ createRenderedNode _animState widget@(Text config) = do
nodeId <- Bridge.createNode Bridge.NodeText
Bridge.setStrProp nodeId Bridge.PropText (tcLabel config)
applyFontConfig nodeId (tcFontConfig config)
applyTextColor nodeId (tcTextColor config)
pure (RenderedLeaf widget nodeId)
createRenderedNode _animState widget@(Button config) = do
nodeId <- Bridge.createNode Bridge.NodeButton
Bridge.setStrProp nodeId Bridge.PropText (bcLabel config)
Bridge.setHandler nodeId Bridge.EventClick (actionId (bcAction config))
applyFontConfig nodeId (bcFontConfig config)
applyTextColor nodeId (bcTextColor config)
pure (RenderedLeaf widget nodeId)
createRenderedNode _animState widget@(TextInput config) = do
nodeId <- Bridge.createNode Bridge.NodeTextInput
Expand All @@ -211,6 +217,7 @@ createRenderedNode _animState widget@(TextInput config) = do
Bridge.setNumProp nodeId Bridge.PropInputType (Int32.toDouble (inputTypeToInt (tiInputType config)))
Bridge.setHandler nodeId Bridge.EventTextChange (onChangeId (tiOnChange config))
applyFontConfig nodeId (tiFontConfig config)
applyTextColor nodeId (tiTextColor config)
when (tiAutoFocus config) $
Bridge.setNumProp nodeId Bridge.PropAutoFocus 1.0
pure (RenderedLeaf widget nodeId)
Expand Down Expand Up @@ -404,6 +411,9 @@ diffRenderNode _animState (Just (RenderedLeaf (Text oldConfig) nodeId)) newWidge
if tcFontConfig oldConfig /= tcFontConfig newConfig
then applyFontConfig nodeId (tcFontConfig newConfig)
else pure ()
if tcTextColor oldConfig /= tcTextColor newConfig
then applyTextColor nodeId (tcTextColor newConfig)
else pure ()
pure (RenderedLeaf newWidget nodeId)

-- Case: Button in-place update — keep native node, only update changed
Expand All @@ -418,6 +428,9 @@ diffRenderNode _animState (Just (RenderedLeaf (Button oldConfig) nodeId)) newWid
if bcFontConfig oldConfig /= bcFontConfig newConfig
then applyFontConfig nodeId (bcFontConfig newConfig)
else pure ()
if bcTextColor oldConfig /= bcTextColor newConfig
then applyTextColor nodeId (bcTextColor newConfig)
else pure ()
pure (RenderedLeaf newWidget nodeId)

-- Case: TextInput in-place update — keep native node to preserve
Expand All @@ -441,6 +454,9 @@ diffRenderNode _animState (Just (RenderedLeaf (TextInput oldConfig) nodeId)) new
if tiFontConfig oldConfig /= tiFontConfig newConfig
then applyFontConfig nodeId (tiFontConfig newConfig)
else pure ()
if tiTextColor oldConfig /= tiTextColor newConfig
then applyTextColor nodeId (tiTextColor newConfig)
else pure ()
pure (RenderedLeaf newWidget nodeId)

-- Case 5/6: Same leaf type with different properties, or completely different
Expand Down
26 changes: 20 additions & 6 deletions src/Hatter/Widget.hs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ module Hatter.Widget
, scrollColumn
, scrollRow
, text
, coloredText
, stack
)
where
Expand Down Expand Up @@ -89,6 +90,13 @@ data TextConfig = TextConfig
-- ^ The text content to display.
, tcFontConfig :: Maybe FontConfig
-- ^ Optional font override.
, tcTextColor :: Maybe Color
-- ^ Optional text color. Lives on the text-bearing configs (like
-- 'tcFontConfig') rather than in 'WidgetStyle', so a text color
-- can never be attached to a container node, where it would
-- silently change nothing (issue #242). Like the font override,
-- a previously set color stays on the native node until another
-- color replaces it.
} deriving (Show, Eq)

-- | Configuration for a tappable button.
Expand All @@ -99,6 +107,8 @@ data ButtonConfig = ButtonConfig
-- ^ Handle for the callback fired when the button is tapped.
, bcFontConfig :: Maybe FontConfig
-- ^ Optional font override.
, bcTextColor :: Maybe Color
-- ^ Optional label color, see 'tcTextColor'.
} deriving (Show, Eq)

-- | The kind of on-screen keyboard to show for a 'TextInput'.
Expand All @@ -120,6 +130,8 @@ data TextInputConfig = TextInputConfig
-- ^ Handle for the callback fired when the user edits the field.
, tiFontConfig :: Maybe FontConfig
-- ^ Optional font override.
, tiTextColor :: Maybe Color
-- ^ Optional text color, see 'tcTextColor'.
, tiAutoFocus :: Bool
-- ^ Whether this input should receive focus when rendered.
-- On Android, defers @requestFocus()@ via @View.post()@ to ensure the
Expand Down Expand Up @@ -179,8 +191,6 @@ data WidgetStyle = WidgetStyle
-- ^ Uniform padding in platform-native units (px on Android, pt on iOS).
, wsTextAlign :: Maybe TextAlignment
-- ^ Horizontal text alignment override.
, wsTextColor :: Maybe Color
-- ^ Text color.
, wsBackgroundColor :: Maybe Color
-- ^ Background color.
, wsTranslateX :: Maybe Double
Expand All @@ -204,7 +214,6 @@ defaultStyle :: WidgetStyle
defaultStyle = WidgetStyle
{ wsPadding = Nothing
, wsTextAlign = Nothing
, wsTextColor = Nothing
, wsBackgroundColor = Nothing
, wsTranslateX = Nothing
, wsTranslateY = Nothing
Expand Down Expand Up @@ -313,7 +322,6 @@ lerpStyle :: Double -> WidgetStyle -> WidgetStyle -> WidgetStyle
lerpStyle t from to = WidgetStyle
{ wsPadding = lerpMaybeDouble (wsPadding from) (wsPadding to)
, wsTextAlign = snapMaybe (wsTextAlign from) (wsTextAlign to)
, wsTextColor = lerpMaybeColor (wsTextColor from) (wsTextColor to)
, wsBackgroundColor = lerpMaybeColor (wsBackgroundColor from) (wsBackgroundColor to)
, wsTranslateX = lerpMaybeDouble (wsTranslateX from) (wsTranslateX to)
, wsTranslateY = lerpMaybeDouble (wsTranslateY from) (wsTranslateY to)
Expand Down Expand Up @@ -488,10 +496,16 @@ data LayoutSettings = LayoutSettings
} deriving (Show, Eq)

text :: Text -> Widget
text txt = Text $ TextConfig { tcLabel = txt, tcFontConfig = Nothing }
text txt = Text $ TextConfig { tcLabel = txt, tcFontConfig = Nothing, tcTextColor = Nothing }

-- | Build a read-only text label with a text color.
coloredText :: Color -> Text -> Widget
coloredText color txt =
Text $ TextConfig { tcLabel = txt, tcFontConfig = Nothing, tcTextColor = Just color }

button :: Text -> Action -> Widget
button txt action = Button $ ButtonConfig { bcLabel = txt, bcAction = action, bcFontConfig = Nothing }
button txt action = Button $ ButtonConfig
{ bcLabel = txt, bcAction = action, bcFontConfig = Nothing, bcTextColor = Nothing }

-- | Wrap a widget in a 'LayoutItem' with no explicit key.
-- The diff algorithm will use the child's list index as its key.
Expand Down
2 changes: 2 additions & 0 deletions test/AnimationDemoMain.hs
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,13 @@ main = do
Text TextConfig
{ tcLabel = "Animated padding"
, tcFontConfig = Nothing
, tcTextColor = Nothing
}
, Button ButtonConfig
{ bcLabel = "Toggle Padding"
, bcAction = toggleAction
, bcFontConfig = Nothing
, bcTextColor = Nothing
}
]
app = MobileApp
Expand Down
2 changes: 1 addition & 1 deletion test/AsyncOomDemoMain.hs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,6 @@ main = do
actionState <- newActionState
startMobileApp MobileApp
{ maContext = loggingMobileContext
, maView = \_userState -> pure (Text TextConfig { tcLabel = "Async loaded", tcFontConfig = Nothing })
, maView = \_userState -> pure (Text TextConfig { tcLabel = "Async loaded", tcFontConfig = Nothing, tcTextColor = Nothing })
, maActionState = actionState
}
3 changes: 2 additions & 1 deletion test/AuthSessionDemoMain.hs
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,11 @@ main = do
-- | Builds a Column with a label and a "Start Login" button.
authSessionDemoView :: Action -> IO Widget
authSessionDemoView onStartLogin = pure $ column
[ Text TextConfig { tcLabel = "AuthSession Demo", tcFontConfig = Nothing }
[ Text TextConfig { tcLabel = "AuthSession Demo", tcFontConfig = Nothing, tcTextColor = Nothing }
, Button ButtonConfig
{ bcLabel = "Start Login"
, bcAction = onStartLogin
, bcFontConfig = Nothing
, bcTextColor = Nothing
}
]
4 changes: 2 additions & 2 deletions test/BleDemoMain.hs
Original file line number Diff line number Diff line change
Expand Up @@ -232,11 +232,11 @@ logDiscoveredCharacteristic discovered = platformLog
-- | Builds a Column with a label and one button per BLE action.
bleDemoView :: [(Text, Action)] -> IO Widget
bleDemoView actions = pure $ column
( Text TextConfig { tcLabel = "BLE Demo", tcFontConfig = Nothing }
( Text TextConfig { tcLabel = "BLE Demo", tcFontConfig = Nothing, tcTextColor = Nothing }
: map actionButton actions
)

-- | A button for one labelled action.
actionButton :: (Text, Action) -> Widget
actionButton (label, action) = Button ButtonConfig
{ bcLabel = label, bcAction = action, bcFontConfig = Nothing }
{ bcLabel = label, bcAction = action, bcFontConfig = Nothing, bcTextColor = Nothing }
3 changes: 2 additions & 1 deletion test/BottomSheetDemoMain.hs
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,11 @@ main = do
-- | Builds a Column with a label and a "Show Actions" button.
bottomSheetDemoView :: Action -> IO Widget
bottomSheetDemoView onShowActions = pure $ column
[ Text TextConfig { tcLabel = "BottomSheet Demo", tcFontConfig = Nothing }
[ Text TextConfig { tcLabel = "BottomSheet Demo", tcFontConfig = Nothing, tcTextColor = Nothing }
, Button ButtonConfig
{ bcLabel = "Show Actions"
, bcAction = onShowActions
, bcFontConfig = Nothing
, bcTextColor = Nothing
}
]
3 changes: 2 additions & 1 deletion test/CameraDemoMain.hs
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,11 @@ main = do
-- | Builds a Column with a label and a "Capture Photo" button.
cameraDemoView :: Action -> IO Widget
cameraDemoView onCapturePhoto = pure $ column
[ Text TextConfig { tcLabel = "Camera Demo", tcFontConfig = Nothing }
[ Text TextConfig { tcLabel = "Camera Demo", tcFontConfig = Nothing, tcTextColor = Nothing }
, Button ButtonConfig
{ bcLabel = "Capture Photo"
, bcAction = onCapturePhoto
, bcFontConfig = Nothing
, bcTextColor = Nothing
}
]
3 changes: 3 additions & 0 deletions test/ConfettiRepDemoMain.hs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ confettiParticle offsetX offsetY =
Text TextConfig
{ tcLabel = "*"
, tcFontConfig = Nothing
, tcTextColor = Nothing
}

-- | Five confetti particles with fixed offsets.
Expand Down Expand Up @@ -72,13 +73,15 @@ main = do
{ bcLabel = "Confetti Active"
, bcAction = triggerAction
, bcFontConfig = Nothing
, bcTextColor = Nothing
}
])
else column
[ Button ButtonConfig
{ bcLabel = "Trigger Confetti"
, bcAction = triggerAction
, bcFontConfig = Nothing
, bcTextColor = Nothing
}
]
app = MobileApp
Expand Down
2 changes: 1 addition & 1 deletion test/ConsumerDepsMain.hs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,6 @@ main = do
actionState <- newActionState
startMobileApp MobileApp
{ maContext = loggingMobileContext
, maView = \_userState -> pure (Text TextConfig { tcLabel = "consumer-deps", tcFontConfig = Nothing })
, maView = \_userState -> pure (Text TextConfig { tcLabel = "consumer-deps", tcFontConfig = Nothing, tcTextColor = Nothing })
, maActionState = actionState
}
7 changes: 4 additions & 3 deletions test/CounterDemoMain.hs
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,15 @@ counterView :: IORef Int -> Action -> Action -> IO Widget
counterView counterState onIncrement onDecrement = do
n <- readIORef counterState
pure $ column
[ Styled (WidgetStyle (Just 16.0) (Just AlignCenter) (Just (Color 255 0 0 255)) (Just (Color 0 255 0 255)) Nothing Nothing Nothing)
[ Styled (WidgetStyle (Just 16.0) (Just AlignCenter) (Just (Color 0 255 0 255)) Nothing Nothing Nothing)
(Text TextConfig
{ tcLabel = "Counter: " <> Text.pack (show n)
, tcFontConfig = Just (FontConfig 24.0)
, tcTextColor = Just (Color 255 0 0 255)
})
, row [ Button ButtonConfig
{ bcLabel = "+", bcAction = onIncrement, bcFontConfig = Nothing }
{ bcLabel = "+", bcAction = onIncrement, bcFontConfig = Nothing, bcTextColor = Nothing }
, Button ButtonConfig
{ bcLabel = "-", bcAction = onDecrement, bcFontConfig = Nothing }
{ bcLabel = "-", bcAction = onDecrement, bcFontConfig = Nothing, bcTextColor = Nothing }
]
]
2 changes: 1 addition & 1 deletion test/DeviceInfoDemoMain.hs
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,4 @@ deviceInfoDemoView = do
-- | A text widget showing a label-value pair.
infoRow :: Text -> Text -> Widget
infoRow label value =
Text TextConfig { tcLabel = label <> ": " <> value, tcFontConfig = Nothing }
Text TextConfig { tcLabel = label <> ": " <> value, tcFontConfig = Nothing, tcTextColor = Nothing }
4 changes: 3 additions & 1 deletion test/DialogDemoMain.hs
Original file line number Diff line number Diff line change
Expand Up @@ -66,15 +66,17 @@ main = do
-- | Builds a Column with a label, a "Show Alert" button, and a "Show Confirm" button.
dialogDemoView :: Action -> Action -> IO Widget
dialogDemoView onShowAlert onShowConfirm = pure $ column
[ Text TextConfig { tcLabel = "Dialog Demo", tcFontConfig = Nothing }
[ Text TextConfig { tcLabel = "Dialog Demo", tcFontConfig = Nothing, tcTextColor = Nothing }
, Button ButtonConfig
{ bcLabel = "Show Alert"
, bcAction = onShowAlert
, bcFontConfig = Nothing
, bcTextColor = Nothing
}
, Button ButtonConfig
{ bcLabel = "Show Confirm"
, bcAction = onShowConfirm
, bcFontConfig = Nothing
, bcTextColor = Nothing
}
]
4 changes: 2 additions & 2 deletions test/FilesDirDemoMain.hs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,6 @@ filesDirDemoView :: IO Widget
filesDirDemoView = do
filesDir <- getAppFilesDir
pure $ column
[ Text TextConfig { tcLabel = "FilesDir Demo", tcFontConfig = Nothing }
, Text TextConfig { tcLabel = pack filesDir, tcFontConfig = Nothing }
[ Text TextConfig { tcLabel = "FilesDir Demo", tcFontConfig = Nothing, tcTextColor = Nothing }
, Text TextConfig { tcLabel = pack filesDir, tcFontConfig = Nothing, tcTextColor = Nothing }
]
2 changes: 1 addition & 1 deletion test/HorizontalScrollDemoMain.hs
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,5 @@ horizontalScrollDemoView :: Action -> Action -> IO Widget
horizontalScrollDemoView noopAction onReachedEnd = pure $ Row (LayoutSettings
( map (\itemNumber -> item (button ("Item " <> Text.pack (show (itemNumber :: Int))) noopAction)) [1..20]
++ [item (Button ButtonConfig
{ bcLabel = "Reached End", bcAction = onReachedEnd, bcFontConfig = Nothing })]
{ bcLabel = "Reached End", bcAction = onReachedEnd, bcFontConfig = Nothing, bcTextColor = Nothing })]
) True)
Loading
Loading