Skip to content

Luix v1.4.0 - The Visuals Update

Choose a tag to compare

@ericplane ericplane released this 20 May 19:07
· 8 commits to main since this release

This release adds three full-fledged visual editors (gradient,
sprite-rect, plus four hover previews), a new diagnostic, and a
sort-props action. Editors are marked Preview in the UI so
users know they're still being polished.

Gradient editor (luix.gradient.*)

A single Edit UIGradient CodeLens above every
e("UIGradient", { … }) element opens a combined side-panel editor
with Color, Transparency, and Rotation. Standalone
ColorSequence.new(...) and NumberSequence.new(...) literals (not
inside a UIGradient) get a focused per-literal editor instead.

  • Colour ramp — drag triangle stops, click the strip to add,
    per-stop colour picker, hover-indicator pill showing the offset.
  • Transparency curve — grid canvas with draggable circle stops
    (X = time, Y = value), envelope shading when non-zero.
  • Rotation slider — −180° to 180°, slider + numeric input.
  • Preview square — combines colour + transparency + rotation
    and multiplies by the parent element's BackgroundColor3
    (Roblox's UIGradient semantics), so what you see matches what
    Roblox renders.
  • Output respects luix.color3.defaultFormat for fromRGB /
    fromHex / new. Default Color = ColorSequence.new(white),
    Transparency = NumberSequence.new(0), and Rotation = 0 are
    omitted from the written-back props block — no noise.
  • Hover previewsColorSequence shows the gradient strip,
    NumberSequence shows the value curve. Toggles:
    luix.gradient.codeLensEnabled (on),
    luix.gradient.previewOnHover (on).
  • PolishShift snaps drag/click to 0.05, hover indicator
    on strip and curve, scroll-wheel + arrow stepping on numeric
    fields, decimal input always renders . regardless of locale,
    blur-revert on invalid input.

Rect editor (luix.rectEditor.*)

An Edit sprite rect CodeLens appears above every
e("ImageLabel" | "ImageButton", { … }) whose Image prop is a
literal rbxassetid://…. Opens a side-panel editor:

  • Thumbnail fetched from thumbnails.roblox.com at the largest
    available size, with a fallback ladder (768 → 512 → 420 → 256 → 150).
  • Draggable rectangle with 8 resize handles; dimmed mask shows
    what gets cropped.
  • X / Y / W / H number fields with Shift = 10× step.
  • Aspect-ratio auto-detect — reads a sibling
    UIAspectRatioConstraint or a fixed-pixel Size = UDim2.fromOffset(…)
    and pre-fills the Frame aspect input.
  • Crop preview overlay — a dashed yellow box inside the
    selection showing exactly what ScaleType.Crop will actually
    render given the frame aspect. Hidden for other ScaleTypes.
  • Native dimension auto-detect via Open Cloud — when
    luix.openCloud.apiKey is set (key needs the legacy-asset:manage
    permission), the editor hits
    apis.roblox.com/asset-delivery-api/v1/assetId/{id}, downloads the
    returned CDN location, and reads the PNG/JPEG header to extract the
    true pixel dimensions. One call per asset, ever — results are
    persisted to globalState.
  • Source dimensions fallback — without an API key (or if the
    lookup fails) the editor uses the thumbnail's natural size and lets
    you type Source W / Source H manually. Manual values are also
    cached per asset, so the typing cost is one-time.
  • Scroll-to-zoom — wheel on the canvas zooms (15% – 300%),
    with a discoverable 🔍 zoom bar (− / % / +) in the corner.
  • Rect can exceed source dimensions — W and H accept up to 4×
    source; the rect can be dragged past the image edge. Overflow is
    signalled with a dashed amber border.
  • Stripped on the "full image" defaultImageRectOffset = (0,0)
    and ImageRectSize = (0,0) are omitted on Apply.

Unused-prop diagnostic (luix.unusedProps.enabled, on by default)

Props declared in a component's parameter type
(props: { Foo: …, Bar: … }) or its @luix-props annotation that
are never read in the body are flagged with the unused-declaration
grey-out style (Hint severity, Unnecessary tag — same treatment
TypeScript uses for unused locals).

Skipped automatically when the body forwards props wholesale
(e(Base, props), for k, v in props do, computed-key indexing)
since static analysis can't determine downstream usage. Squiggle
lands on the field name inside the type annotation when possible,
otherwise on the function definition line.

Visual hover previews (luix.hoverPreviews.enabled, on by default)

Inline SVG previews rendered straight from your literal values —
no network requests, no caching:

  • TweenInfo.new(...) — 240×140 graph of the easing curve.
    All 12 EasingStyle × 4 EasingDirection combinations are
    implemented as math functions. Below the curve: duration,
    repeat-count, reverses, and delay summary.
  • e("UIPadding", { … }) — box visualisation with the inner
    content area indented by the configured PaddingTop / Right /
    Bottom / Left. Each non-zero side gets its pixel value labeled.
  • e("UICorner", { … }) — rounded rectangle rendered at the
    configured CornerRadius.
  • e("UIStroke", { … }) — sample box with the stroke applied
    at the configured Thickness / Color / Transparency.

Sort props by category (luix.sortProps.*)

  • Code action — Right-click anywhere inside a props table → 💡
    Sort props by category. Reorders props by category (then by
    canonical order within each category, stable on ties).
  • Format-on-saveluix.sortProps.onSave (default false).
    When enabled, every props table in the document is sorted on
    save. Off by default so saving a teammate's file doesn't
    reshape their layout.
  • Configurable orderluix.sortProps.categoryOrder (string
    array) lets you reorder or remove categories. Defaults:
    Identification → Layout → Style → Visibility → Image → Text →
    Behavior → Events → Refs → Children → Other.
  • Computed-key aware — captures [React.Event.Activated],
    [OnEvent "…"], [Children], etc. Vide-style plain identifier
    events (Activated = function() … end) are recognised too.
  • Comment-safe — tables containing -- comments are skipped
    so comments never get detached. Idempotent: re-sorting a sorted
    table is a no-op (no spurious save edits).

Per-class prop type overrides

Some Roblox prop names mean different things on different classes
(e.g. Frame.Style → Enum.FrameStyle, but
GuiButton.Style → Enum.ButtonStyle). 1.3.0's global PROP_TYPES
map could only hold one value per prop name and silently picked
the wrong enum on Frame.

New PROP_TYPE_OVERRIDES map keyed by class, plus a
getPropType(className, propName) helper that walks the class
hierarchy. The hover tooltip, completion value-snippet, and the
wrong-enum diagnostic now all resolve Style (and any future
conflicts) per-class. Adding more is a one-line entry per class.

Wrap-in code action — fixes (regressions from 1.3.0)

  • Multi-element selection — selecting UIPadding + TextLabel
    now wraps those two siblings in a new container, instead of
    wrapping their parent Frame (the previous behaviour found the
    smallest call containing the selection, which is always the
    parent for multi-element selections).
  • Indentation — wrapped lines no longer get double-indented.
    The previous indentLines(text, baseIndent + step) prepended
    both the base AND the new step to lines that already had their
    original indent baked in, producing 4-tab-deep Name = … etc.

Expanded class & prop catalogue (src/data.ts)

Built-in classHierarchy and PROP_TYPES significantly expanded
to track newer Roblox additions and previously-missing props:

  • GuiBase2dRootLocalizationTable, SelectionBehaviorDown/Left/Right/Up, SelectionGroup, SelectionChanged event.
  • GuiObjectInputSink, NextSelection*, SelectionImageObject.
  • GuiButtonHoverHapticEffect, PressHapticEffect, Style.
  • FrameStyle.
  • VideoFrameMaximumResolution, RollOffMaxDistance/MinDistance/Mode.
  • TextLabel / TextButtonOpenTypeFeatures.
  • TextBox — re-rooted under GuiObject (was TextLabel) to
    match Roblox's actual hierarchy; full text-prop set mirrored.
  • BillboardGuiAdornee, ResetOnSpawn, TabKeyboardNavigation.
  • SurfaceGuiActive, TabKeyboardNavigation.
  • ScreenGuiTabKeyboardNavigation.
  • UIStrokeBorderOffset, BorderStrokePosition, StrokeSizingMode, ZIndex.
  • InstanceArchivable.
  • And 40+ new PROP_TYPES entries for the above, covering
    Rect, Camera, Player, LocalizationTable, plus several
    new enum types (BorderStrokePosition, HapticEffect,
    InputSink, NormalId, RollOffMode, SelectionBehavior,
    StrokeSizingMode, VideoSampleSize).