geometry_kit_widgets 0.3.0
geometry_kit_widgets: ^0.3.0 copied to clipboard
Flutter widgets for geometry_kit shapes — CustomPainter-backed, styled, themable, and composable. Includes circle, ellipse, rectangle, triangle, polygon, line, a multi-shape canvas, and a 3D scene for [...]
0.3.0 #
Adds 3D scenes for geometry_kit_3d solids on top of 0.2.0.
Note: 0.2.0 was published from sources that had not reached the repository; they are restored here, so everything listed under 0.2.0 is still included.
3D scenes #
GeoScene— rendersgeometry_kit_3dsolids on a Flutter canvas with anOrbitCamera, flat shading from aDirectionalLight, back-face culling and painter's-algorithm depth sorting. Drag to orbit, pinch/scroll to zoom.GeoSolid,GeoPath3(withGeoPath3.grid),GeoLabel3scene nodes.- Picking:
onTap/onHoverreturn aSceneHit;onDragSolidmoves a grabbed solid on the horizontal plane. ScenePainter,SceneMeshCache,SceneMeshexposed for custom painting. The mesh cache is keyed by solid value (LRU, 256 entries), so solids rebuilt inbuild()are not re-triangulated.- Faces and paths crossing the camera's near plane are clipped, not
dropped; pinch zoom stops at
OrbitCamera.minDistance. SceneViewport(camera + size):rayAt,project,pick,pickHandle,onPlane, anddragPlane, which falls back to a camera-facing plane when the requested one is seen edge-on.GeoScene.interaction: aSceneInteractiongets raw pointer events before the camera and can claim a gesture; a claimed gesture wins the arena, so neither the camera nor an enclosing scroll view reacts. A second finger cancels the claim.onDragSolidis built on it and drags on a camera-facing plane at low pitch instead of flinging the solid.GeoSolid.transform(Transform3): the mesh is cached for the local solid and placed at paint time, so moving a solid does not re-triangulate it or flood the mesh cache.GeoSolid.worldSolidgives the placed solid for exact queries; picking uses it.- Inside a scroll view, drags that start on a
GeoSceneorbit the camera instead of scrolling the page (vertical drags used to scroll). WithenableOrbit: falsethe page scrolls as before. GeoHandlescene node: a fixed-size dot or square at a 3D point, drawn on top and picked on screen.
Solid gradients #
GeoSolid.gradienttakes aSolidGradient, used in place ofShapeStyle.fillColor. It is laid across the solid's local bounding box, so it moves, turns and scales withGeoSolid.transform.ShapeStyle.opacityand edge outlines still apply.Anchor3names a point in that box, -1 to 1 per axis (x left→right, y bottom→top, z back→front), with constants such asAnchor3.top,.left,.frontand.topLeftFront.Anchor3.resolveandAnchor3.ofconvert to and from local points.SolidGradient.linear(start:, end:)runs from one anchor to another (top to bottom by default), so the anchors pick the side it starts from.SolidGradient.radial(center:, radius:)runs outward from a point: the top by default, withradiusreaching right across the solid when left out (RadialSolidGradient.effectiveRadius). Both takecolorsand optionalstops;reversedruns either the other way.- Colours are worked out per vertex and shaded per face. Gradients that
bend within a face (radial, three or more colours, or ends inside the
box; see
SolidGradient.needsRefinement) draw onSceneMesh.refined, which splits big faces at most twice while keeping normals and sharp edges. A two-colour linear gradient across the box is exact as is and adds no triangles. SceneMesh.vertexColorskeeps the last gradient's colours, so orbiting or dragging a solid does not recompute them.
Fixes #
- Fill and stroke colours with their own alpha are no longer forced opaque:
the colour's alpha is now multiplied by
ShapeStyle.opacityinstead of replaced by it.
0.2.0 #
Bundles three internal milestones (decorative styling, theme merge + animation, widget clipping) into one published release on top of 0.1.1.
Breaking #
ShapeStyle.strokeWidth,opacity,strokeCap,strokeJoinare now nullable (double?/StrokeCap?/StrokeJoin?). Constructor defaults are unchanged (1.0/1.0/StrokeCap.butt/StrokeJoin.miter), so most call sites compile unmodified. Code that reads these fields unconditionally (e.g.style.strokeWidth.toString()) must adoptstyle.strokeWidth ?? 1.0for the equivalent fallback. Painters inside the package already do this.ShapeStyleTheme.resolve(context, fallback)is nowresolve(context, explicit, {fallback}). Whenexplicitis non-null the resolver returnsexplicit.merge(ambient)— fields the override leavesnullfall back to the ambient theme rather than fully replacing it. Whenexplicitisnull, the ambient theme (or the optionalfallback) is returned, matching the previous behaviour. All built-inGeo*widgets switched fromstyle ?? ShapeStyleTheme.resolve(context, const ShapeStyle())toShapeStyleTheme.resolve(context, style).
Added #
- Decorative styling
ShapeStyle.fillGradientandShapeStyle.strokeGradient— accept any FlutterGradient(linear, radial, sweep). Gradients win over solid colors when both are set on the same channel. Resolved against the painted shape's axis-aligned bounding rect at paint time.ShapeStyle.gradientFilled(Gradient)convenience constructor.PaintBuildertypedef +ShapeStyle.fillPaintBuilder/ShapeStyle.strokePaintBuilder— escape hatch for advanced Skia features (MaskFilter,ImageFilter,BlendMode, custom shaders) without expanding the primary style surface.
- Theme merge + animation
ShapeStyle.merge(ShapeStyle? base)— field-level fallback. Used byShapeStyleTheme.resolveso a child override and an ambient theme combine per-field instead of all-or-nothing.ShapeStyle.lerp(a, b, t)static method. Smoothly interpolates colors and thestrokeWidth/opacityscalars; gradients, dash patterns, enum fields, andPaintBuilders snap att >= 0.5.ShapeStyleTween—Tween<ShapeStyle?>drivingShapeStyle.lerp.AnimatedShapeStylewidget — sibling ofAnimatedDefaultTextStyle. Animates aShapeStyleover aDurationand supplies the interpolated value to descendants throughShapeStyleTheme.
- Widget clipping
shapeToPath(Shape, CoordinateMapper)— top-level helper that returns the canvas-spacePathfor any supportedgeometry_kitshape (Circle,Ellipse,Rectangle,Triangle,Quadrilateral,Polygon,Line). Uses the same path bodies the painters render; throwsUnsupportedErrorfor unhandled shape types.ShapeClipper—CustomClipper<Path>wrapping a shape for direct use with Flutter'sClipPath. Accepts anygeometry_kitShapeplus aCoordinateMapper(defaults toidentity).ShapeWidget— host widget that composes a shape outline, an optional clipped child, and an optionalShapeStyleoverlay. Stroke draws above the clipped child so clip-edge anti-aliasing does not eat the outline.
- Tap + hover interactivity
GeoCircle,GeoEllipse,GeoRectangle,GeoTriangle,GeoQuadrilateral,GeoPolygongain optionalonTapandonHoverChangedparameters. Hit-test is shape-precise viashapeToPath(...).contains(localPosition)— pointer events outside the shape outline are ignored even when they land in the host widget rect.StyledShapegains matchingonTap/onHoverChanged.GeometryCanvaswalks shapes top-down on each pointer event and dispatches to the first hit; the topmost (last-painted) shape wins both tap and hover.- Hover supports both mouse pointers (via
MouseRegion) and stylus/touch hover (viaListener.onPointerHover).GeoLinedoes not expose interaction in v1 (zero-area path makes shape-precise hit-test degenerate; reach for aGeo*filled shape orGestureDetectorif line interaction is needed).
- Examples
- Solar system demo upgraded with radial-gradient sun, gradient-shaded
planets (sun-tracking day/night highlight), pulsing halo via
MaskFilter.blur, and live sliders for orbit speed and planet scale. - Clipping demo gains a "ShapeWidget" section (gradient circle, text in triangle, pentagon over grid) plus an interactive image-clip playground (vertex-count + rotation sliders, source picker for network photo / Flutter logo / gradient).
- Solar system demo upgraded with radial-gradient sun, gradient-shaded
planets (sun-tracking day/night highlight), pulsing halo via
Changed #
ShapeStyle.opacitynow applies only to solid colors — encode alpha into gradient color stops directly.- Painters refactored to share path construction via the new
shapeToPathhelper. Behaviour identical; rectangle'scornerRadiusbranch stays inRectanglePaintersince corner radius is a widget-level concept, not a field ongeo.Rectangle.LinePainterretains its own path build.
Notes #
GeoLinedoes not supportstrokeGradient(zero-area bounds make shader resolution degenerate). Asserted in debug, ignored in release.GeoLinedoes supportstrokePaintBuilderfor blur / mask effects.PaintBuilderequality is by function identity. Use top-level orstaticbuilders when value-equality matters forshouldRepaint.- Field-level theme merging only kicks in when an override explicitly sets
fields to
null(the constructor's non-null defaults otherwise win). Use the raw constructor — notcopyWith— to writenullinto a field, sincecopyWithcannot transition a value to null. ShapeWidgetdoes not auto-fit a shape to widget bounds. Geometry coordinates must already match the rendered widget space, or be transformed viamapper. Sizing happens through the optionalsizeparameter or the parent's bounded constraints.- Clipping a
Lineis degenerate (zero-area path);ShapeClipperdocuments the case rather than asserting. - Hit-test arena:
Geo*widgets andGeometryCanvasuse opaqueGestureDetectors. Taps inside the host widget rect but outside the shape outline are still consumed (no callback fires) — siblings under the widget rect won't see those taps. Set the widget's size tightly to the shape if pass-through is needed.
0.1.1 #
- update git info
0.1.0 #
Initial release. Phase 1 of the widget library.
Widgets #
GeoCircle,GeoEllipse,GeoRectangle,GeoTriangle,GeoQuadrilateral,GeoPolygon,GeoLine— declarativeCustomPainter-backed widgets, each with flat and.fromGeometryconstructors.GeometryCanvas+StyledShape— multi-shape rendering on a single canvas with type-switch dispatch.
Styling #
ShapeStyle— fill, stroke, opacity, dash pattern, cap, join (value equality,copyWith).ShapeStyle.filled/ShapeStyle.strokedconvenience constructors.ShapeStyleTheme—InheritedWidgetfor default style inheritance viaShapeStyle.of(context)/ShapeStyleTheme.resolve.DashPattern— sharedPathMetrics-based dash drawing across all painters.- Color opacity uses
withValues(alpha:)(Flutter 3.27+).
Coordinates #
CoordinateMapper—identity(default, Y-down),yUp(size),centered(size, {yUp}).- Locked into v0.1 so painter signatures stay stable.
Composition #
- Every widget exposes
clipBehavior(defaultClip.hardEdge) — shapes never bleed past widget bounds unless explicitly opted in withClip.none. GeometryCanvas.backgroundColordefaults tonull— transparent canvas layers cleanly over other widgets.- Internal
GeoShapeContainerkeepsClipRect+RepaintBoundary+Semanticsconsistent across every widget.
Other #
- Dartdoc on every public class, constructor, and parameter.
Semantics(label: ...)on every widget for accessibility.RepaintBoundarywraps everyCustomPaint.- Eight passing widget + unit tests.
