git.iko.soy/rowtype-yoga/purescript-yoga-react-native
on main help sign in
Telegram-style chat colors: green bubbles, tinted wallpaper background
commit607c095bb0f0b4975590d6cbf047c35ae56d6ad8 authorMark <mark.eibes@gmail.com> dateSat 14 Feb 2026, 09:10 (8 months ago) parentaac5dd2
4 files changed+170−3
example-macos/macos/YogaReactExample-macOS/RCTMacOSControlsManager.m+125 example-macos/src/Main.purs+12−3 src/Yoga/React/Native/MacOS/PatternBackground.js+16 src/Yoga/React/Native/MacOS/PatternBackground.purs+17
example-macos/macos/YogaReactExample-macOS/RCTMacOSControlsManager.m+125
@@ -1631,3 +1631,128 @@ RCT_EXPORT_MODULE(MacOSAnimatedImage)
16311631RCT_EXPORT_VIEW_PROPERTY(source, NSString)
16321632RCT_EXPORT_VIEW_PROPERTY(animating, BOOL)
16331633@end
1634+
1635+// ── Section 22: Pattern Background ──────────────────────────────────────────
1636+// A tiled pattern background using Core Graphics — Telegram-style chat wallpaper
1637+
1638+@interface RCTPatternBackgroundView : NSView
1639+@property (nonatomic, copy) NSString *patternColor;
1640+@property (nonatomic, copy) NSString *backgroundColor2;
1641+@property (nonatomic, assign) CGFloat patternOpacity;
1642+@property (nonatomic, assign) CGFloat patternScale;
1643+@end
1644+
1645+@implementation RCTPatternBackgroundView
1646+
1647+- (instancetype)initWithFrame:(NSRect)frame {
1648+ if (self = [super initWithFrame:frame]) {
1649+ _patternColor = @"#FFFFFF";
1650+ _backgroundColor2 = @"#17212B";
1651+ _patternOpacity = 0.06;
1652+ _patternScale = 1.0;
1653+ }
1654+ return self;
1655+}
1656+
1657+- (NSSize)intrinsicContentSize {
1658+ return NSMakeSize(NSViewNoIntrinsicMetric, NSViewNoIntrinsicMetric);
1659+}
1660+
1661+- (void)setPatternColor:(NSString *)patternColor {
1662+ _patternColor = patternColor;
1663+ [self setNeedsDisplay:YES];
1664+}
1665+
1666+- (void)setBackgroundColor2:(NSString *)backgroundColor2 {
1667+ _backgroundColor2 = backgroundColor2;
1668+ [self setNeedsDisplay:YES];
1669+}
1670+
1671+- (void)setPatternOpacity:(CGFloat)patternOpacity {
1672+ _patternOpacity = patternOpacity;
1673+ [self setNeedsDisplay:YES];
1674+}
1675+
1676+- (void)setPatternScale:(CGFloat)patternScale {
1677+ _patternScale = patternScale;
1678+ [self setNeedsDisplay:YES];
1679+}
1680+
1681+- (NSColor *)colorFromHex:(NSString *)hex {
1682+ hex = [hex stringByReplacingOccurrencesOfString:@"#" withString:@""];
1683+ unsigned int val = 0;
1684+ [[NSScanner scannerWithString:hex] scanHexInt:&val];
1685+ return [NSColor colorWithRed:((val >> 16) & 0xFF) / 255.0
1686+ green:((val >> 8) & 0xFF) / 255.0
1687+ blue:(val & 0xFF) / 255.0
1688+ alpha:1.0];
1689+}
1690+
1691+- (BOOL)isFlipped { return YES; }
1692+
1693+- (void)drawRect:(NSRect)dirtyRect {
1694+ NSColor *bgColor = [self colorFromHex:_backgroundColor2];
1695+ [bgColor setFill];
1696+ NSRectFill(dirtyRect);
1697+
1698+ NSColor *pColor = [[self colorFromHex:_patternColor] colorWithAlphaComponent:_patternOpacity];
1699+ CGFloat s = _patternScale;
1700+ CGFloat tile = 24.0 * s;
1701+
1702+ [pColor setStroke];
1703+
1704+ // Draw a subtle geometric pattern — diagonal crosses on a grid
1705+ CGFloat startX = floor(dirtyRect.origin.x / tile) * tile;
1706+ CGFloat startY = floor(dirtyRect.origin.y / tile) * tile;
1707+ CGFloat endX = NSMaxX(dirtyRect);
1708+ CGFloat endY = NSMaxY(dirtyRect);
1709+
1710+ NSBezierPath *path = [NSBezierPath bezierPath];
1711+ [path setLineWidth:0.5 * s];
1712+
1713+ for (CGFloat x = startX; x < endX; x += tile) {
1714+ for (CGFloat y = startY; y < endY; y += tile) {
1715+ CGFloat cx = x + tile / 2.0;
1716+ CGFloat cy = y + tile / 2.0;
1717+ CGFloat arm = 3.0 * s;
1718+
1719+ // Small cross
1720+ [path moveToPoint:NSMakePoint(cx - arm, cy)];
1721+ [path lineToPoint:NSMakePoint(cx + arm, cy)];
1722+ [path moveToPoint:NSMakePoint(cx, cy - arm)];
1723+ [path lineToPoint:NSMakePoint(cx, cy + arm)];
1724+
1725+ // Small diamond at offset
1726+ CGFloat dx = x + tile * 0.15;
1727+ CGFloat dy = y + tile * 0.65;
1728+ CGFloat dm = 1.5 * s;
1729+ [path moveToPoint:NSMakePoint(dx, dy - dm)];
1730+ [path lineToPoint:NSMakePoint(dx + dm, dy)];
1731+ [path lineToPoint:NSMakePoint(dx, dy + dm)];
1732+ [path lineToPoint:NSMakePoint(dx - dm, dy)];
1733+ [path closePath];
1734+ }
1735+ }
1736+ [path stroke];
1737+}
1738+
1739+- (void)insertReactSubview:(NSView *)subview atIndex:(NSInteger)atIndex {
1740+ [super insertReactSubview:subview atIndex:atIndex];
1741+}
1742+
1743+- (void)layout {
1744+ [super layout];
1745+ [self setNeedsDisplay:YES];
1746+}
1747+
1748+@end
1749+
1750+@interface RCTPatternBackgroundViewManager : RCTViewManager @end
1751+@implementation RCTPatternBackgroundViewManager
1752+RCT_EXPORT_MODULE(MacOSPatternBackground)
1753+- (NSView *)view { return [[RCTPatternBackgroundView alloc] initWithFrame:CGRectZero]; }
1754+RCT_EXPORT_VIEW_PROPERTY(patternColor, NSString)
1755+RCT_EXPORT_VIEW_PROPERTY(backgroundColor2, NSString)
1756+RCT_EXPORT_VIEW_PROPERTY(patternOpacity, CGFloat)
1757+RCT_EXPORT_VIEW_PROPERTY(patternScale, CGFloat)
1758+@end
example-macos/src/Main.purs+12−3
@@ -35,6 +35,7 @@ import Yoga.React.Native.MacOS.ContextMenu (nativeContextMenu)
3535import Yoga.React.Native.MacOS.FilePicker (nativeFilePicker)
3636import Yoga.React.Native.MacOS.VideoPlayer (nativeVideoPlayer)
3737import Yoga.React.Native.MacOS.AnimatedImage (nativeAnimatedImage)
38+import Yoga.React.Native.MacOS.PatternBackground (nativePatternBackground)
3839import Yoga.React.Native.Style as Style
3940
4041main :: Effect Unit
@@ -833,9 +834,17 @@ chatTab = component "ChatTab" \p -> React.do
833834 <> Style.style { borderBottomWidth: 0.5, borderColor: p.dimFg, backgroundColor: "transparent" }
834835 }
835836 [ text { style: tw "text-base font-semibold" <> Style.style { color: p.fg } } activeContact ]
836− , nativeScrollView { scrollToBottom: length messages, style: tw "flex-1" <> Style.style { backgroundColor: chatBg } }
837− ( view { style: tw "py-2" }
838− (mapWithIndex messageBubble messages)
837+ , nativePatternBackground
838+ { patternColor: if p.isDark then "#FFFFFF" else "#000000"
839+ , backgroundColor2: chatBg
840+ , patternOpacity: if p.isDark then 0.04 else 0.06
841+ , patternScale: 1.0
842+ , style: tw "flex-1"
843+ }
844+ ( nativeScrollView { scrollToBottom: length messages, style: tw "flex-1" <> Style.style { backgroundColor: "transparent" } }
845+ ( view { style: tw "py-2 pr-3" }
846+ (mapWithIndex messageBubble messages)
847+ )
839848 )
840849 , if null matchingStickers then stickerBar
841850 else stickerPicker matchingStickers
src/Yoga/React/Native/MacOS/PatternBackground.jsnew file+16
@@ -0,0 +1,16 @@
1+import {
2+ View,
3+ Platform,
4+ UIManager,
5+ requireNativeComponent,
6+} from "react-native";
7+
8+var c = View;
9+if (Platform.OS === "macos") {
10+ try {
11+ if (UIManager.getViewManagerConfig("MacOSPatternBackground"))
12+ c = requireNativeComponent("MacOSPatternBackground");
13+ } catch (e) {}
14+}
15+
16+export const _patternBackgroundImpl = c;
src/Yoga/React/Native/MacOS/PatternBackground.pursnew file+17
@@ -0,0 +1,17 @@
1+module Yoga.React.Native.MacOS.PatternBackground (nativePatternBackground, NativePatternBackgroundAttributes) where
2+
3+import React.Basic (ReactComponent)
4+import Yoga.React.Native.Attributes (BaseAttributes)
5+import Yoga.React.Native.Internal (FFINativeComponent, createNativeElement)
6+
7+foreign import _patternBackgroundImpl :: forall props. ReactComponent props
8+
9+nativePatternBackground :: FFINativeComponent NativePatternBackgroundAttributes
10+nativePatternBackground = createNativeElement _patternBackgroundImpl
11+
12+type NativePatternBackgroundAttributes = BaseAttributes
13+ ( patternColor :: String
14+ , backgroundColor2 :: String
15+ , patternOpacity :: Number
16+ , patternScale :: Number
17+ )