Skip to main content

platform-renderers

How each platform renderer maps UI::View types to native elements across Web, macOS, iOS, and Android

インストールへ移動

ソース情報

リポジトリ
amberframework/asset_pipeline
ソースの最終更新活動
2026年3月3日 18:45
検出された SKILL.md の言語
英語
スター
5
フォーク
0

インストール方法

デフォルトでは、最初にソースを確認する Prompt が選択されています。直接コマンドに切り替えるか、ローカルコピーをダウンロードすることもできます。

ソースファイルを確認

インストールを決める前に、SKILL.md と SkillsMP に表示されている付属ファイルをお読みください。

SKILL.md を表示中

SKILL.md
ソースの指示 · 読み取り専用プレビュー
name
platform-renderers
description
How each platform renderer maps UI::View types to native elements across Web, macOS, iOS, and Android
version
2.0
# Platform Renderers Each platform renderer is a subclass of `UI::PlatformVisitor` that translates the abstract `UI::View` tree into platform-native elements. Only one renderer is compiled into any given binary, selected at compile time via `flag?()`. ## Compile-Time Selection ```crystal {% if flag?(:macos) %} require "./renderers/appkit_renderer" alias PlatformRenderer = UI::AppKit::Renderer {% elsif flag?(:ios) %} require "./renderers/uikit_renderer" alias PlatformRenderer = UI::UIKit::Renderer {% elsif flag?(:android) %} require "./renderers/android_renderer" alias PlatformRenderer = UI::Android::Renderer {% else %} require "./renderers/web_renderer" alias PlatformRenderer = UI::Web::Renderer {% end %} ``` --- ## Web::Renderer **Target file:** `src/ui/renderers/web_renderer.cr` **Compiled when:** No platform flag set (default), or explicitly for web targets The web renderer delegates to the existing `Components::Elements` classes in the asset_pipeline shard. It does NOT reimplement HTML generation -- it maps each `UI::View` to the appropriate `Components::Elements` class and applies CSS styles. ### View-to-Element Mapping | UI::View | Components::Elements class | CSS applied | |----------|---------------------------|-------------| | `Label` | `Elements::Span` | `font-family`, `font-size`, `font-weight`, `color`, `text-align`, line clamping | | `Button` | `Elements::Button` | Foreground color, `data-action` attribute for reactive dispatch | | `VStack` | `Elements::Div` | `display:flex; flex-direction:column; gap:{spacing}px; align-items:{alignment}` | | `HStack` | `Elements::Div` | `display:flex; flex-direction:row; gap:{spacing}px; align-items:{alignment}` | | `ZStack` | `Elements::Div` | `position:relative` with `position:absolute` on children | | `Image` | `Elements::Img` | `object-fit` mapped from `ContentMode` | | `TextField` | `Elements::Input` | `.text` or `.password` factory depending on `secure_entry` | | `ScrollView` | `Elements::Div` | `overflow-x`/`overflow-y` based on scroll axes | | `Spacer` | `Elements::Div` | `flex:1 1 0%; min-height:{min_length}px` or `min-width:{min_length}px` | | `Toggle` | `Elements::Input` | `type="checkbox" role="switch"` | | `Checkbox` | `Elements::Input` | `type="checkbox"` with label | | `RadioGroup` | `Elements::Div` | Multiple `<input type="radio" name="...">` with shared name | | `Slider` | `Elements::Input` | `type="range" min max step` | | `Stepper` | `Elements::Input` | `type="number" min max step` | | `SegmentedControl` | `Elements::Div` | CSS button group with `aria-pressed` on active segment | | `NavigationStack` | `Elements::Div` | JS-router `<div>` visibility toggling, browser history API | | `NavigationLink` | `Elements::A` | `<a href="...">` with SPA router integration | | `TabView` | `Elements::Div` | `role="tablist"` nav + `role="tabpanel"` content divs | | `NavigationSplitView` | `Elements::Div` | `display:grid; grid-template-columns: {sidebar_width}px 1fr` | | `Toolbar` | `Elements::Header` | `<header>` or `<nav>` with action `<button>` elements | | `ProgressView` | `Elements::Div` | `<progress>` (determinate) or CSS spinner (indeterminate Circular) | | `ActivityIndicator` | `Elements::Div` | CSS keyframe animation spinner | | `Alert` | `Elements::Div` | `<dialog>` element or modal overlay div | | `Picker` | `Elements::Select` | `<select>` with `<option>` children | | `IconButton` | `Elements::Button` | `<button>` with icon `<img>` or CSS icon class | | `ListView` | `Elements::Div` | `<ul>` or `<div>` with repeated child elements and section headers | | `SecureField` | `Elements::Input` | `type="password"` | | `SearchField` | `Elements::Input` | `type="search"` with cancel button | | `TextArea` | `Elements::Div` | `<textarea rows="N">` | | `TextEditor` | `Elements::Div` | `<textarea>` with optional syntax highlighting via CSS/JS | | `DatePicker` | `Elements::Input` | `type="date"`, `type="time"`, or `type="datetime-local"` per mode | | `TimePicker` | `Elements::Input` | `type="time"` with step for minute_interval | | `Grid` | `Elements::Div` | `display:grid; grid-template-columns:...` with child cells | | `Form` | `Elements::Form` | `<form>` with `<fieldset>`/`<legend>` per section | | `AsyncImage` | `Elements::Img` | `<img loading="lazy">` with placeholder swap via JS | | `RichText` | `Elements::Div` | Multiple `<span>` with inline styles per Span record | | `LinkButton` | `Elements::A` | `<a href="...">` styled as button | | `MenuButton` | `Elements::Div` | `<button>` + hidden `<ul>` dropdown, shown on click | | `ToggleButton` | `Elements::Button` | `<button aria-pressed="true/false">` | | `Sheet` | `Elements::Div` | Fixed overlay from bottom with CSS animation, `is_presented` toggles visibility | | `Popover` | `Elements::Div` | Absolutely positioned `<div>` relative to anchor, Popover API when available | | `ConfirmationDialog` | `Elements::Div` | `<dialog>` with confirm/cancel buttons | | `Snackbar` | `Elements::Div` | Fixed-position `<div>` at bottom with CSS slide/fade animation | | `Card` | `Elements::Div` | `border-radius`, `box-shadow`, `background-color` | | `Surface` | `Elements::Div` | `background-color`, optional `border` | | `Divider` | `Elements::Div` | `<hr>` or `<div style="height:1px;background:...">` | | `GlassBackground` | `Elements::Div` | `backdrop-filter:blur(Xpx); background:rgba(...,0.5)` | | `Circle` | `Elements::Div` | `border-radius:50%; width/height={size}px` or SVG `<circle>` | | `Rectangle` | `Elements::Div` | `<div>` with explicit width/height and background | | `RoundedRectangle` | `Elements::Div` | `border-radius:{corner_radius}px` | | `Capsule` | `Elements::Div` | `border-radius:9999px` | | `Canvas` | `Elements::Div` | `<canvas>` element; operations serialized to JS 2D context calls | | `PathView` | `Elements::Div` | SVG `<path>` element (stub) | | `MapView` | `Elements::Div` | `<div id="map">` + Leaflet.js / Google Maps JS API | | `ChartView` | `Elements::Div` | `<canvas>` + Chart.js or D3.js integration | | `WebViewComponent` | `Elements::Div` | `<iframe src="...">` | | `ColorPicker` | `Elements::Input` | `type="color"` | | `VideoPlayer` | `Elements::Div` | `<video src="..." controls>` | | `Tooltip` | `Elements::Div` | `title` attribute or custom `:hover` CSS tooltip | ### CSS Strategy - Layout CSS uses utility classes: `ui-vstack`, `ui-hstack`, `ui-spacer`, etc. - Inline styles handle dynamic values (spacing, colors, fonts) - Base class modifier properties (`corner_radius`, `shadow_*`, `border_*`, `blur_radius`) are applied as inline CSS on every rendered element - The renderer produces standard HTML that works with the existing `ReactiveHandler` WebSocket system ### Integration with Components System The `UI::ViewAdapter` class bridges the UI view tree into the reactive component system: ```crystal class UI::ViewAdapter < Components::Reactive::ReactiveComponent def render_content : String renderer = UI::Web::Renderer.new @root.accept(renderer) renderer.output end end ``` Existing `StatelessComponent`, `StatefulComponent`, and `ReactiveComponent` subclasses continue working without modifications. --- ## AppKit::Renderer (macOS) **Target file:** `src/ui/renderers/appkit_renderer.cr` **Compiled when:** `flag?(:macos)` — you MUST pass `-Dmacos` to crystal-alpha **Bridge:** `src/ui/native/objc_bridge.m` (compile with `clang -c ... -fno-objc-arc`) **Build prerequisites:** ```bash # 1. Compile the ObjC bridge (required — provides typed objc_msgSend wrappers) clang -c src/ui/native/objc_bridge.m -o objc_bridge.o -fno-objc-arc # 2. Build with -Dmacos flag and link the bridge crystal-alpha build app.cr -o app -Dmacos \ --link-flags="objc_bridge.o -framework AppKit -framework Foundation -lobjc" ``` Maps `UI::View` types to AppKit `NSView` subclasses via the ObjC runtime C API. ### View-to-NSView Mapping | UI::View | AppKit class | Configuration | |----------|-------------|---------------| | `Label` | `NSTextField` | Non-editable, `setBezeled:NO`, `setDrawsBackground:NO` | | `Button` | `NSButton` | `NSBezelStyleRounded`, target-action via `CrystalActionDispatcher` | | `VStack` | `NSStackView` | `orientation: .vertical`, `spacing`, `alignment` | | `HStack` | `NSStackView` | `orientation: .horizontal`, `spacing`, `alignment` | | `ZStack` | `NSView` | Children added as subviews with manual frame layout | | `Image` | `NSImageView` | `imageScaling` mapped from `ContentMode` | | `TextField` | `NSTextField` | Editable, delegate for change notifications | | `ScrollView` | `NSScrollView` | `hasVerticalScroller`/`hasHorizontalScroller` | | `Spacer` | (no view) | Flexible space via NSStackView gravity areas | | `Toggle` | `NSButton` | `setButtonType: NSButtonTypeSwitch` (switch style) or `NSButtonTypeCheck` (checkbox style) | | `Checkbox` | `NSButton` | `setButtonType: NSButtonTypeCheck` (buttonType=check) | | `RadioGroup` | `NSMatrix` or group of `NSButton` | `setButtonType: NSButtonTypeRadio` per option | | `Slider` | `NSSlider` | `minValue`, `maxValue`, `numberOfTickMarks` for step | | `Stepper` | `NSStepper` | `minValue`, `maxValue`, `increment` | | `SegmentedControl` | `NSSegmentedControl` | `trackingMode: .selectOne`, one segment per label | | `NavigationStack` | Custom `NSViewController` stack | Push via `presentAsModalWindow:` or custom stack | | `NavigationLink` | `NSButton` | Action triggers push on parent NavigationStack | | `TabView` | `NSTabViewController` | One `NSTabViewItem` per `Tab` record | | `NavigationSplitView` | `NSSplitViewController` | `NSSplitViewItem` per column | | `Toolbar` | `NSToolbar` | `NSToolbarItem` per ToolbarItem; window decoration | | `ProgressView` | `NSProgressIndicator` | `style: .bar` (Linear) or `.spinning` (Circular); `isIndeterminate` when value=nil |
GitHubで見る
この SKILL.md は非常に大きいため、SkillsMP では最初のセクションだけを表示しています。 GitHubで見る