Skip to content

Commit a20ad8e

Browse files
authored
Merge pull request #246 from jappeace-sloth/feat/width-style-property
Add wsWidth: a fixed-width style property through all bridges
2 parents aa9976f + cea0a80 commit a20ad8e

15 files changed

Lines changed: 91 additions & 17 deletions

‎Changelog.md‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@
22

33
## Unreleased
44

5+
### Added
6+
7+
- `wsWidth`: a fixed-width style property (platform-native units,
8+
like `wsPadding`) applied through all platform bridges and
9+
animatable via keyframes, so sibling rows can align into table
10+
columns (#245).
11+
512
### Changed
613

714
- Missing fields in record construction are compile errors now

‎cbits/ui_bridge_android.c‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -741,6 +741,19 @@ static void android_set_num_prop(int32_t nodeId, int32_t propId, double value)
741741
LOGI("setNumProp(node=%d, padding=%d)", nodeId, px);
742742
break;
743743
}
744+
case UI_PROP_WIDTH: {
745+
/* Fixed width in raw pixels (same unit convention as
746+
* UI_PROP_PADDING); height stays WRAP_CONTENT. The gravity
747+
* case above uses the same LayoutParams pattern. */
748+
jint px = (jint)value;
749+
jobject layoutParams = (*env)->NewObject(env,
750+
g_class_ViewGroup_LayoutParams, g_ctor_ViewGroup_LayoutParams,
751+
px, (jint)-2); /* width, WRAP_CONTENT */
752+
(*env)->CallVoidMethod(env, view, g_method_setLayoutParams, layoutParams);
753+
(*env)->DeleteLocalRef(env, layoutParams);
754+
LOGI("setNumProp(node=%d, width=%d)", nodeId, px);
755+
break;
756+
}
744757
case UI_PROP_INPUT_TYPE: {
745758
/* Haskell 0 = InputText -> Android TYPE_CLASS_TEXT (1)
746759
* Haskell 1 = InputNumber -> Android TYPE_CLASS_NUMBER |

‎include/UIBridge.h‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
#define UI_PROP_TRANSLATE_Y 10
4040
#define UI_PROP_AUTO_FOCUS 11
4141
#define UI_PROP_TOUCH_PASSTHROUGH 12
42+
#define UI_PROP_WIDTH 13
4243

4344
/* Event types */
4445
#define UI_EVENT_CLICK 0

‎ios/Hatter/UIBridgeIOS.m‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -580,6 +580,24 @@ static void ios_set_num_prop(int32_t nodeId, int32_t propId, double value)
580580
LOGI("setNumProp(node=%d, padding=%.1f)", nodeId, value);
581581
break;
582582
}
583+
case UI_PROP_WIDTH: {
584+
/* Fixed width in points; a constraint overrides the
585+
* intrinsic content width inside the UIStackView layout.
586+
* Deactivate any width constraint from an earlier render
587+
* before pinning the new one. */
588+
CGFloat pt = (CGFloat)value;
589+
NSArray<NSLayoutConstraint *> *existing = [view.constraints copy];
590+
for (NSLayoutConstraint *constraint in existing) {
591+
if (constraint.firstAttribute == NSLayoutAttributeWidth
592+
&& constraint.firstItem == view
593+
&& constraint.secondItem == nil) {
594+
constraint.active = NO;
595+
}
596+
}
597+
[view.widthAnchor constraintEqualToConstant:pt].active = YES;
598+
LOGI("setNumProp(node=%d, width=%.1f)", nodeId, value);
599+
break;
600+
}
583601
case UI_PROP_SCALE_TYPE: {
584602
/* Haskell 0 = ScaleFit, 1 = ScaleFill, 2 = ScaleNone */
585603
if ([view isKindOfClass:[UIImageView class]]) {

‎src/Hatter/Animation.hs‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ defaultStyleEmpty = WidgetStyle
187187
, wsBackgroundColor = Nothing
188188
, wsTranslateX = Nothing
189189
, wsTranslateY = Nothing
190+
, wsWidth = Nothing
190191
, wsTouchPassthrough = Nothing
191192
}
192193

@@ -217,6 +218,8 @@ interpolateStyle nodeId fromStyle toStyle progress = do
217218
(wsTranslateX fromStyle) (wsTranslateX toStyle)
218219
lerpNumProp Bridge.PropTranslateY
219220
(wsTranslateY fromStyle) (wsTranslateY toStyle)
221+
lerpNumProp Bridge.PropWidth
222+
(wsWidth fromStyle) (wsWidth toStyle)
220223
-- TouchPassthrough is boolean — snaps to target, no interpolation.
221224
case wsTouchPassthrough toStyle of
222225
Just enabled ->

‎src/Hatter/Render.hs‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,9 @@ applyStyle nodeId style = do
170170
case wsTranslateY style of
171171
Just ty -> Bridge.setNumProp nodeId Bridge.PropTranslateY ty
172172
Nothing -> pure ()
173+
case wsWidth style of
174+
Just width -> Bridge.setNumProp nodeId Bridge.PropWidth width
175+
Nothing -> pure ()
173176
case wsTouchPassthrough style of
174177
Just enabled -> Bridge.setNumProp nodeId Bridge.PropTouchPassthrough
175178
(if enabled then 1.0 else 0.0)

‎src/Hatter/UIBridge.hs‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ data PropId
8686
| PropTranslateY
8787
| PropAutoFocus
8888
| PropTouchPassthrough
89+
| PropWidth
8990
deriving (Show, Eq, Enum, Bounded)
9091

9192
-- | Map a 'PropId' to its C integer code.
@@ -110,6 +111,7 @@ propIdToInt PropTranslateX = 9
110111
propIdToInt PropTranslateY = 10
111112
propIdToInt PropAutoFocus = 11
112113
propIdToInt PropTouchPassthrough = 12
114+
propIdToInt PropWidth = 13
113115

114116
-- | Event types corresponding to @UI_EVENT_*@ in @UIBridge.h@.
115117
data EventType

‎src/Hatter/Widget.hs‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,10 @@ data WidgetStyle = WidgetStyle
201201
, wsTranslateY :: Maybe Double
202202
-- ^ Vertical translation offset in platform-native units.
203203
-- Moves the widget without affecting sibling layout.
204+
, wsWidth :: Maybe Double
205+
-- ^ Fixed width in platform-native units (px on Android, pt on
206+
-- iOS), like 'wsPadding'. Meaningful on any node, so sibling
207+
-- rows can align into table columns (#245).
204208
, wsTouchPassthrough :: Maybe Bool
205209
-- ^ When 'True', the widget does not intercept touches, allowing
206210
-- sibling views underneath (in a 'Stack') to receive them.
@@ -217,6 +221,7 @@ defaultStyle = WidgetStyle
217221
, wsBackgroundColor = Nothing
218222
, wsTranslateX = Nothing
219223
, wsTranslateY = Nothing
224+
, wsWidth = Nothing
220225
, wsTouchPassthrough = Nothing
221226
}
222227

@@ -325,6 +330,7 @@ lerpStyle t from to = WidgetStyle
325330
, wsBackgroundColor = lerpMaybeColor (wsBackgroundColor from) (wsBackgroundColor to)
326331
, wsTranslateX = lerpMaybeDouble (wsTranslateX from) (wsTranslateX to)
327332
, wsTranslateY = lerpMaybeDouble (wsTranslateY from) (wsTranslateY to)
333+
, wsWidth = lerpMaybeDouble (wsWidth from) (wsWidth to)
328334
, wsTouchPassthrough = snapMaybe (wsTouchPassthrough from) (wsTouchPassthrough to)
329335
}
330336
where

‎test/CounterDemoMain.hs‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import Data.Text qualified as Text
1010
import Foreign.Ptr (Ptr)
1111
import Hatter (startMobileApp, platformLog, loggingMobileContext, MobileApp(..), newActionState, runActionM, createAction, Action)
1212
import Hatter.AppContext (AppContext)
13-
import Hatter.Widget (ButtonConfig(..), Color(..), FontConfig(..), TextAlignment(..), TextConfig(..), Widget(..), WidgetStyle(..), column, row)
13+
import Hatter.Widget (ButtonConfig(..), Color(..), FontConfig(..), TextAlignment(..), TextConfig(..), Widget(..), WidgetStyle(..), column, defaultStyle, row)
1414

1515
main :: IO (Ptr AppContext)
1616
main = do
@@ -32,7 +32,7 @@ counterView :: IORef Int -> Action -> Action -> IO Widget
3232
counterView counterState onIncrement onDecrement = do
3333
n <- readIORef counterState
3434
pure $ column
35-
[ Styled (WidgetStyle (Just 16.0) (Just AlignCenter) (Just (Color 0 255 0 255)) Nothing Nothing Nothing)
35+
[ Styled ((defaultStyle { wsPadding = Just 16.0, wsTextAlign = Just AlignCenter, wsBackgroundColor = Just (Color 0 255 0 255) }))
3636
(Text TextConfig
3737
{ tcLabel = "Counter: " <> Text.pack (show n)
3838
, tcFontConfig = Just (FontConfig 24.0)

‎test/Test/ActionTests.hs‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ import Hatter
2020
, runActionM
2121
)
2222
import Hatter.Widget
23-
( ButtonConfig(..)
23+
( defaultStyle
24+
, ButtonConfig(..)
2425
, InputType(..)
2526
, LayoutSettings(..)
2627
, TextConfig(..)
@@ -294,7 +295,7 @@ incrementalRenderTests = testGroup "Incremental rendering"
294295

295296
, testCase "styled unchanged keeps same node ID" $ do
296297
((), rs) <- withActions (pure ())
297-
let style = WidgetStyle (Just 10.0) Nothing Nothing Nothing Nothing Nothing
298+
let style = defaultStyle { wsPadding = Just 10.0 }
298299
widget = Styled style (Text TextConfig { tcLabel = "styled", tcFontConfig = Nothing, tcTextColor = Nothing })
299300
renderWidget rs widget
300301
tree1 <- readIORef (rsRenderedTree rs)
@@ -310,7 +311,7 @@ incrementalRenderTests = testGroup "Incremental rendering"
310311

311312
, testCase "styled child change updates in-place" $ do
312313
((), rs) <- withActions (pure ())
313-
let style = WidgetStyle (Just 10.0) Nothing Nothing Nothing Nothing Nothing
314+
let style = defaultStyle { wsPadding = Just 10.0 }
314315
widget1 = Styled style (Text TextConfig { tcLabel = "before", tcFontConfig = Nothing, tcTextColor = Nothing })
315316
renderWidget rs widget1
316317
tree1 <- readIORef (rsRenderedTree rs)
@@ -329,7 +330,7 @@ incrementalRenderTests = testGroup "Incremental rendering"
329330
, testCase "styled child type change reapplies style" $ do
330331
(clickAction, rs) <- withActions $
331332
createAction (pure ())
332-
let style = WidgetStyle (Just 10.0) Nothing Nothing Nothing Nothing Nothing
333+
let style = defaultStyle { wsPadding = Just 10.0 }
333334
widget1 = Styled style (Text TextConfig { tcLabel = "txt", tcFontConfig = Nothing, tcTextColor = Nothing })
334335
renderWidget rs widget1
335336
-- Re-render with different child type but same style

0 commit comments

Comments
 (0)