From d5a55852cfe1c05ad29e3f9a2383ef7dd44f2879 Mon Sep 17 00:00:00 2001 From: SpinyOwl Date: Mon, 11 May 2026 23:49:24 +0200 Subject: [PATCH 001/121] Add project structure documentation --- AGENTS_CODE_STYLE.md | 80 ++++ PROJECT_STRUCTURE.md | 92 ++++ docs/project-structure/README.md | 70 ++++ docs/project-structure/packages/com/README.md | 20 + .../packages/com/spinyowl/README.md | 20 + .../packages/com/spinyowl/spinygui/README.md | 21 + .../com/spinyowl/spinygui/core/README.md | 39 ++ .../spinygui/core/animation/README.md | 31 ++ .../spinyowl/spinygui/core/backend/README.md | 20 + .../spinygui/core/backend/renderer/README.md | 25 ++ .../core/backend/renderer/lwjgl/README.md | 20 + .../backend/renderer/lwjgl/nanovg/README.md | 46 ++ .../renderer/lwjgl/nanovg/util/README.md | 31 ++ .../spinygui/core/clipboard/README.md | 17 + .../spinyowl/spinygui/core/cursor/README.md | 31 ++ .../spinyowl/spinygui/core/event/README.md | 166 ++++++++ .../spinygui/core/event/listener/README.md | 17 + .../spinygui/core/event/processor/README.md | 24 ++ .../com/spinyowl/spinygui/core/font/README.md | 45 ++ .../spinyowl/spinygui/core/image/README.md | 17 + .../spinyowl/spinygui/core/input/README.md | 88 ++++ .../spinygui/core/input/impl/README.md | 31 ++ .../spinyowl/spinygui/core/layout/README.md | 53 +++ .../spinygui/core/layout/impl/README.md | 59 +++ .../com/spinyowl/spinygui/core/node/README.md | 61 +++ .../spinygui/core/node/intersection/README.md | 31 ++ .../spinygui/core/node/layout/README.md | 31 ++ .../spinyowl/spinygui/core/parser/README.md | 32 ++ .../spinygui/core/parser/impl/README.md | 46 ++ .../spinygui/core/parser/impl/css/README.md | 21 + .../core/parser/impl/css/antlr/README.md | 52 +++ .../core/parser/impl/css/visitor/README.md | 66 +++ .../spinyowl/spinygui/core/style/README.md | 27 ++ .../spinygui/core/style/manager/README.md | 24 ++ .../spinygui/core/style/stylesheet/README.md | 108 +++++ .../style/stylesheet/annotation/README.md | 17 + .../core/style/stylesheet/atrule/README.md | 17 + .../core/style/stylesheet/impl/README.md | 24 ++ .../core/style/stylesheet/property/README.md | 157 +++++++ .../core/style/stylesheet/selector/README.md | 49 +++ .../stylesheet/selector/combinator/README.md | 45 ++ .../stylesheet/selector/pseudoclass/README.md | 17 + .../selector/pseudoelement/README.md | 31 ++ .../stylesheet/selector/simple/README.md | 38 ++ .../core/style/stylesheet/term/README.md | 73 ++++ .../core/style/stylesheet/util/README.md | 17 + .../spinygui/core/style/types/README.md | 91 ++++ .../core/style/types/background/README.md | 31 ++ .../core/style/types/border/README.md | 24 ++ .../spinygui/core/style/types/flex/README.md | 52 +++ .../core/style/types/length/README.md | 31 ++ .../spinyowl/spinygui/core/system/README.md | 22 + .../spinygui/core/system/event/README.md | 132 ++++++ .../core/system/event/listener/README.md | 199 +++++++++ .../core/system/event/processor/README.md | 24 ++ .../core/system/event/provider/README.md | 24 ++ .../spinygui/core/system/font/README.md | 67 +++ .../spinygui/core/system/font/impl/README.md | 31 ++ .../spinygui/core/system/input/README.md | 31 ++ .../com/spinyowl/spinygui/core/time/README.md | 17 + .../com/spinyowl/spinygui/core/util/README.md | 59 +++ .../com/spinyowl/spinygui/demo/README.md | 21 + .../spinyowl/spinygui/demo/complex/README.md | 24 ++ .../spinyowl/spinygui/demo/simple/README.md | 24 ++ tools/generate_structure_docs.js | 395 ++++++++++++++++++ 65 files changed, 3346 insertions(+) create mode 100644 AGENTS_CODE_STYLE.md create mode 100644 PROJECT_STRUCTURE.md create mode 100644 docs/project-structure/README.md create mode 100644 docs/project-structure/packages/com/README.md create mode 100644 docs/project-structure/packages/com/spinyowl/README.md create mode 100644 docs/project-structure/packages/com/spinyowl/spinygui/README.md create mode 100644 docs/project-structure/packages/com/spinyowl/spinygui/core/README.md create mode 100644 docs/project-structure/packages/com/spinyowl/spinygui/core/animation/README.md create mode 100644 docs/project-structure/packages/com/spinyowl/spinygui/core/backend/README.md create mode 100644 docs/project-structure/packages/com/spinyowl/spinygui/core/backend/renderer/README.md create mode 100644 docs/project-structure/packages/com/spinyowl/spinygui/core/backend/renderer/lwjgl/README.md create mode 100644 docs/project-structure/packages/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/README.md create mode 100644 docs/project-structure/packages/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/util/README.md create mode 100644 docs/project-structure/packages/com/spinyowl/spinygui/core/clipboard/README.md create mode 100644 docs/project-structure/packages/com/spinyowl/spinygui/core/cursor/README.md create mode 100644 docs/project-structure/packages/com/spinyowl/spinygui/core/event/README.md create mode 100644 docs/project-structure/packages/com/spinyowl/spinygui/core/event/listener/README.md create mode 100644 docs/project-structure/packages/com/spinyowl/spinygui/core/event/processor/README.md create mode 100644 docs/project-structure/packages/com/spinyowl/spinygui/core/font/README.md create mode 100644 docs/project-structure/packages/com/spinyowl/spinygui/core/image/README.md create mode 100644 docs/project-structure/packages/com/spinyowl/spinygui/core/input/README.md create mode 100644 docs/project-structure/packages/com/spinyowl/spinygui/core/input/impl/README.md create mode 100644 docs/project-structure/packages/com/spinyowl/spinygui/core/layout/README.md create mode 100644 docs/project-structure/packages/com/spinyowl/spinygui/core/layout/impl/README.md create mode 100644 docs/project-structure/packages/com/spinyowl/spinygui/core/node/README.md create mode 100644 docs/project-structure/packages/com/spinyowl/spinygui/core/node/intersection/README.md create mode 100644 docs/project-structure/packages/com/spinyowl/spinygui/core/node/layout/README.md create mode 100644 docs/project-structure/packages/com/spinyowl/spinygui/core/parser/README.md create mode 100644 docs/project-structure/packages/com/spinyowl/spinygui/core/parser/impl/README.md create mode 100644 docs/project-structure/packages/com/spinyowl/spinygui/core/parser/impl/css/README.md create mode 100644 docs/project-structure/packages/com/spinyowl/spinygui/core/parser/impl/css/antlr/README.md create mode 100644 docs/project-structure/packages/com/spinyowl/spinygui/core/parser/impl/css/visitor/README.md create mode 100644 docs/project-structure/packages/com/spinyowl/spinygui/core/style/README.md create mode 100644 docs/project-structure/packages/com/spinyowl/spinygui/core/style/manager/README.md create mode 100644 docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/README.md create mode 100644 docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/annotation/README.md create mode 100644 docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/atrule/README.md create mode 100644 docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/impl/README.md create mode 100644 docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/property/README.md create mode 100644 docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/selector/README.md create mode 100644 docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/selector/combinator/README.md create mode 100644 docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/selector/pseudoclass/README.md create mode 100644 docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/selector/pseudoelement/README.md create mode 100644 docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/selector/simple/README.md create mode 100644 docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/term/README.md create mode 100644 docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/util/README.md create mode 100644 docs/project-structure/packages/com/spinyowl/spinygui/core/style/types/README.md create mode 100644 docs/project-structure/packages/com/spinyowl/spinygui/core/style/types/background/README.md create mode 100644 docs/project-structure/packages/com/spinyowl/spinygui/core/style/types/border/README.md create mode 100644 docs/project-structure/packages/com/spinyowl/spinygui/core/style/types/flex/README.md create mode 100644 docs/project-structure/packages/com/spinyowl/spinygui/core/style/types/length/README.md create mode 100644 docs/project-structure/packages/com/spinyowl/spinygui/core/system/README.md create mode 100644 docs/project-structure/packages/com/spinyowl/spinygui/core/system/event/README.md create mode 100644 docs/project-structure/packages/com/spinyowl/spinygui/core/system/event/listener/README.md create mode 100644 docs/project-structure/packages/com/spinyowl/spinygui/core/system/event/processor/README.md create mode 100644 docs/project-structure/packages/com/spinyowl/spinygui/core/system/event/provider/README.md create mode 100644 docs/project-structure/packages/com/spinyowl/spinygui/core/system/font/README.md create mode 100644 docs/project-structure/packages/com/spinyowl/spinygui/core/system/font/impl/README.md create mode 100644 docs/project-structure/packages/com/spinyowl/spinygui/core/system/input/README.md create mode 100644 docs/project-structure/packages/com/spinyowl/spinygui/core/time/README.md create mode 100644 docs/project-structure/packages/com/spinyowl/spinygui/core/util/README.md create mode 100644 docs/project-structure/packages/com/spinyowl/spinygui/demo/README.md create mode 100644 docs/project-structure/packages/com/spinyowl/spinygui/demo/complex/README.md create mode 100644 docs/project-structure/packages/com/spinyowl/spinygui/demo/simple/README.md create mode 100644 tools/generate_structure_docs.js diff --git a/AGENTS_CODE_STYLE.md b/AGENTS_CODE_STYLE.md new file mode 100644 index 00000000..00b52f68 --- /dev/null +++ b/AGENTS_CODE_STYLE.md @@ -0,0 +1,80 @@ +# Agent Code Style And Principles + +This project is a modular Java GUI library with a browser-engine-like architecture. Treat the codebase as a DOM/CSS/layout/event/rendering pipeline, not as a widget toolkit with isolated controls. + +## Architectural Principles + +- Keep the core backend-agnostic. Core nodes, styles, layout, input, events, fonts, and parsers should not depend on NanoVG/OpenGL rendering details. +- Preserve the pipeline shape: parse/build nodes, parse CSS, resolve style, run layout, translate system events, dispatch GUI events, render an already styled and laid-out `Frame`. +- Prefer interface boundaries for services and put defaults in `impl` packages. Existing examples include parser, layout, style manager, event processor, font service, mouse service, and shortcut registry. +- Model GUI state explicitly on nodes and services. Hover, focus, pressed state, scroll offsets, layout boxes, mouse positions, and resolved styles are mutable runtime state. +- Keep CSS concepts typed where possible. Use `Property`, `Term`, `Selector`, `Length`, `Color`, and style type classes rather than passing raw strings past parser/property boundaries. +- Treat generated ANTLR files under `core/src/main/java/.../parser/impl/css/antlr` as generated. Change `CSS3.g4` and regenerate outputs instead of manually editing generated parser classes. + +## Java Style + +- Java modules are deliberate. Update `module-info.java` when adding exported APIs or new module dependencies. +- The build uses Java 21 source/target compatibility. README still says Java 15+, so verify before changing compatibility claims. +- Formatting follows the checked-in IntelliJ Google Java style. Keep two-space indentation and readable wrapped method chains. +- Lombok is part of the project style. Existing code uses `@Getter`, `@Setter`, `@RequiredArgsConstructor`, `@NonNull`, `@ToString`, `@NoArgsConstructor`, and `@Slf4j`. +- Lombok accessors are fluent and not chained. Prefer `element.box()` over `getBox()` and avoid introducing chained setter assumptions. +- Keep constructors small and use required-constructor dependency injection where the surrounding package already does so. +- Use static utility classes for cross-cutting calculations only when they match existing patterns such as `LayoutUtils`, `StyleUtils`, `NodeUtilities`, `TextUtil`, and NanoVG utility classes. +- Do not add broad frameworks for dependency injection, rendering, parsing, or event dispatch. Existing composition is manual and explicit. + +## Package Conventions + +- `core.node` owns the DOM-like tree model. Add tree behavior here only when it belongs to all nodes/elements, not to rendering or parsing. +- `core.style.stylesheet` owns the CSS object model. Add new CSS properties through property providers and the property store, not by special-casing `ResolvedStyle` first. +- `core.style.types` owns strongly typed CSS values. Add new value types here when a property needs a reusable domain object. +- `core.parser.impl.css.visitor` is the semantic CSS conversion layer. The grammar recognizes more CSS than visitors support; update visitors and property providers together. +- `core.layout.impl` owns layout algorithms and box updates. Keep rendering out of layout; layout should populate geometry and layout-parent relationships. +- `core.system.event.*` handles raw platform events. Convert them into GUI events before application listeners see them. +- `core.event.*` handles GUI-level events and listener dispatch. +- `core.backend.*` should consume `Frame`, `Element`, `Text`, style, and box data; it should not mutate core model state except through clearly intentional renderer lifecycle effects. + +## CSS And Style Rules + +- Respect rule ordering: default rules, frame stylesheets ordered by specificity, then inline style declarations. +- Preserve specificity semantics when adding selectors. Selector classes should implement matching and specificity consistently. +- Be explicit about unsupported CSS. Current visitors often return `null` or throw `NotImplementedException` for partial features; do not silently pretend full browser CSS support exists. +- The ANTLR grammar supports more constructs than the semantic model. When enabling a grammar feature, add model classes, visitors, property conversion, defaults, tests, and serialization behavior as needed. +- `ResolvedStyle` is a typed facade over a generic map. Add typed accessors for supported properties that are used outside property conversion. + +## Event And Input Rules + +- Keep the two-stage event pipeline intact: system event queue first, GUI event queue second. +- Event dispatch is exact-class based in current processors. Do not assume superclass listeners receive subclass events unless you change and test that behavior. +- System listeners should update state and emit GUI events in the same place, following existing cursor, mouse, key, char, scroll, and window listener patterns. +- Mouse hit-testing should respect `NodeUtilities` and each node's `Intersection` strategy. +- Keyboard behavior should pass through `KeyboardLayout` and `ShortcutRegistry`; avoid hard-coding native key codes in GUI-level code. + +## Layout Rules + +- Layout writes to `Box`, offset parent, layout child nodes, scroll sizes, client sizes, and text cursor positions. +- Keep block, flex, text, and none layout behavior separate. Add shared calculations to `LayoutUtils` only when multiple algorithms need them. +- Flex layout delegates to Yoga. Extend the mapping between SpinyGUI style values and Yoga properties rather than reimplementing flexbox manually. +- Use `Length` and style accessors for dimensions, border, padding, margin, and positioning. Avoid raw numeric shortcuts unless the existing method is already in pixel space. + +## Rendering Rules + +- `Renderer` is intentionally minimal: `initialize`, `render`, `destroy`. +- NanoVG code must manage native resources carefully. Follow try-with-resources patterns for `NVGColor`, `NVGPaint`, and other stack/calloc resources where applicable. +- Rendering should traverse the layout tree, not the raw child tree, because positioned and normal-flow children may differ. +- Current NanoVG text rendering is placeholder/debug-like. Do not document it or build on it as complete glyph rendering without implementing and testing actual text drawing. +- Current border rendering is not full side-specific CSS border support. Be explicit when extending it. + +## Testing And Verification + +- For parser changes, test both successful parse/model conversion and unsupported/invalid syntax behavior. +- For style changes, test property parsing, defaults, inheritance if applicable, shorthand expansion, and resolved style output. +- For layout changes, test box geometry, scroll/client size updates, positioned nodes, and text wrapping/metrics. +- For event changes, test queue behavior and listener side effects. Existing tests under `core/src/test/java/.../system/event/listener` are the closest pattern. +- For backend changes, run a demo when possible and verify lifecycle/resource cleanup paths. + +## Known Caution Areas + +- Some CSS features are grammar-recognized but semantically unsupported or partially supported. +- Generated ANTLR outputs are currently modified in the working tree; do not overwrite them accidentally. +- `GeneralSiblingSelector`, clickable-only recursive hit testing, text metrics edge handling, and exact-class event dispatch have potential edge cases noted in current code analysis. +- Demo classes contain exploratory assertions and embedded HTML/CSS examples. Do not treat them as complete production tests. diff --git a/PROJECT_STRUCTURE.md b/PROJECT_STRUCTURE.md new file mode 100644 index 00000000..cc497266 --- /dev/null +++ b/PROJECT_STRUCTURE.md @@ -0,0 +1,92 @@ +# Project Structure + +SpinyGUI is a modular Java GUI library with a browser-engine-like split between node tree, CSS parsing/style resolution, layout, event processing, and rendering backends. + +Generated package documents are stored under `docs/project-structure/packages/`. Each package document lists direct classes first, then links to child packages so the documentation can be read from deepest packages upward. + +## Gradle Modules + +- `core` - Core DOM-like node model, CSS parser/style system, layout, events, input, fonts, animation, and platform abstraction. +- `core.backend` - Renderer backend API shared by concrete rendering implementations. +- `core.backend.lwjgl.nanovg` - LWJGL/NanoVG renderer implementation for drawing the core scene graph. +- `demo.simple` - Small launcher-style examples for exercising the aggregate SpinyGUI module. +- `demo.complex` - GLFW/LWJGL demo harness and NanoVG example runner. +- `spinygui` - Aggregate module that re-exports the core and default backend modules. + +## Main Subsystems + +- Node tree: core node classes model frames, elements, text, attributes, parent/child links, pseudo-state, and box geometry. +- Style and CSS: stylesheet model, selectors, property providers, ANTLR visitors, typed CSS values, and `ResolvedStyle` convert parsed CSS into values usable by layout/rendering. +- Layout: layout contracts and implementations calculate box-model rectangles, text metrics, normal-flow/positioned layout trees, scroll sizes, and client sizes. +- Events and input: system events are translated by system listeners/processors into application events and node state changes. +- Fonts and metrics: font service/storage abstractions load platform fonts and expose text metrics for layout/rendering. +- Rendering: backend SPI defines `Renderer`; the LWJGL/NanoVG backend traverses layout nodes and draws elements, borders, and text. +- Demos: simple and complex demo modules exercise the aggregate API and NanoVG backend. + +## Package Index + +- [com](docs/project-structure/packages/com/README.md) - 0 direct class(es); Top-level Java namespace folder for project packages. +- [com.spinyowl](docs/project-structure/packages/com/spinyowl/README.md) - 0 direct class(es); SpinyOwl namespace folder. +- [com.spinyowl.spinygui](docs/project-structure/packages/com/spinyowl/spinygui/README.md) - 0 direct class(es); SpinyGUI namespace folder aggregating core, backend, and demo packages. +- [com.spinyowl.spinygui.core](docs/project-structure/packages/com/spinyowl/spinygui/core/README.md) - 1 direct class(es); Top-level core configuration and shared entry points for the GUI engine. +- [com.spinyowl.spinygui.core.animation](docs/project-structure/packages/com/spinyowl/spinygui/core/animation/README.md) - 3 direct class(es); Frame-time animation contracts and a simple animator loop. +- [com.spinyowl.spinygui.core.backend](docs/project-structure/packages/com/spinyowl/spinygui/core/backend/README.md) - 0 direct class(es); Backend namespace folder for renderer APIs and implementations. +- [com.spinyowl.spinygui.core.backend.renderer](docs/project-structure/packages/com/spinyowl/spinygui/core/backend/renderer/README.md) - 1 direct class(es); Renderer SPI consumed by backend implementations. +- [com.spinyowl.spinygui.core.backend.renderer.lwjgl](docs/project-structure/packages/com/spinyowl/spinygui/core/backend/renderer/lwjgl/README.md) - 0 direct class(es); Package for lwjgl related classes. +- [com.spinyowl.spinygui.core.backend.renderer.lwjgl.nanovg](docs/project-structure/packages/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/README.md) - 4 direct class(es); NanoVG renderer orchestration and specialized element/text/border renderers. +- [com.spinyowl.spinygui.core.backend.renderer.lwjgl.nanovg.util](docs/project-structure/packages/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/util/README.md) - 3 direct class(es); NanoVG drawing and color helpers. +- [com.spinyowl.spinygui.core.clipboard](docs/project-structure/packages/com/spinyowl/spinygui/core/clipboard/README.md) - 1 direct class(es); Clipboard abstraction used by platform integrations. +- [com.spinyowl.spinygui.core.cursor](docs/project-structure/packages/com/spinyowl/spinygui/core/cursor/README.md) - 3 direct class(es); Cursor model and cursor service abstraction. +- [com.spinyowl.spinygui.core.event](docs/project-structure/packages/com/spinyowl/spinygui/core/event/README.md) - 21 direct class(es); Application-level events emitted to nodes and event targets. +- [com.spinyowl.spinygui.core.event.listener](docs/project-structure/packages/com/spinyowl/spinygui/core/event/listener/README.md) - 1 direct class(es); Generic event listener contract for application events. +- [com.spinyowl.spinygui.core.event.processor](docs/project-structure/packages/com/spinyowl/spinygui/core/event/processor/README.md) - 2 direct class(es); Dispatch logic for routing application events to node listeners. +- [com.spinyowl.spinygui.core.font](docs/project-structure/packages/com/spinyowl/spinygui/core/font/README.md) - 5 direct class(es); CSS-like font value objects: family, size, stretch, style, and weight. +- [com.spinyowl.spinygui.core.image](docs/project-structure/packages/com/spinyowl/spinygui/core/image/README.md) - 1 direct class(es); Image abstraction used by style and rendering layers. +- [com.spinyowl.spinygui.core.input](docs/project-structure/packages/com/spinyowl/spinygui/core/input/README.md) - 10 direct class(es); Input domain model for keyboard, mouse, shortcuts, and user-facing key mappings. +- [com.spinyowl.spinygui.core.input.impl](docs/project-structure/packages/com/spinyowl/spinygui/core/input/impl/README.md) - 3 direct class(es); Default mutable implementations of input services. +- [com.spinyowl.spinygui.core.layout](docs/project-structure/packages/com/spinyowl/spinygui/core/layout/README.md) - 5 direct class(es); Layout contracts, layout context, and text/element layout interfaces. +- [com.spinyowl.spinygui.core.layout.impl](docs/project-structure/packages/com/spinyowl/spinygui/core/layout/impl/README.md) - 7 direct class(es); Concrete layout algorithms and utilities for block, flex, none, text, and layout tree updates. +- [com.spinyowl.spinygui.core.node](docs/project-structure/packages/com/spinyowl/spinygui/core/node/README.md) - 6 direct class(es); DOM-like node hierarchy: frames, elements, empty elements, text nodes, and builders. +- [com.spinyowl.spinygui.core.node.intersection](docs/project-structure/packages/com/spinyowl/spinygui/core/node/intersection/README.md) - 3 direct class(es); Hit-testing strategy objects for node intersection checks. +- [com.spinyowl.spinygui.core.node.layout](docs/project-structure/packages/com/spinyowl/spinygui/core/node/layout/README.md) - 3 direct class(es); Box-model geometry value objects used by layout and rendering. +- [com.spinyowl.spinygui.core.parser](docs/project-structure/packages/com/spinyowl/spinygui/core/parser/README.md) - 2 direct class(es); Parser interfaces for HTML-like node trees and stylesheets. +- [com.spinyowl.spinygui.core.parser.impl](docs/project-structure/packages/com/spinyowl/spinygui/core/parser/impl/README.md) - 4 direct class(es); Default parser implementations and parser factory code. +- [com.spinyowl.spinygui.core.parser.impl.css](docs/project-structure/packages/com/spinyowl/spinygui/core/parser/impl/css/README.md) - 0 direct class(es); CSS parser namespace containing generated ANTLR artifacts and handwritten semantic visitors. +- [com.spinyowl.spinygui.core.parser.impl.css.antlr](docs/project-structure/packages/com/spinyowl/spinygui/core/parser/impl/css/antlr/README.md) - 6 direct class(es); Generated ANTLR CSS3 lexer/parser/listener/visitor artifacts. Regenerate from the grammar instead of hand-editing. +- [com.spinyowl.spinygui.core.parser.impl.css.visitor](docs/project-structure/packages/com/spinyowl/spinygui/core/parser/impl/css/visitor/README.md) - 8 direct class(es); ANTLR visitors that convert CSS parse trees into stylesheet, selector, declaration, and term model objects. +- [com.spinyowl.spinygui.core.style](docs/project-structure/packages/com/spinyowl/spinygui/core/style/README.md) - 1 direct class(es); Resolved style state applied to nodes after rule matching and property conversion. +- [com.spinyowl.spinygui.core.style.manager](docs/project-structure/packages/com/spinyowl/spinygui/core/style/manager/README.md) - 2 direct class(es); Style manager contract and implementation for applying stylesheets to node trees. +- [com.spinyowl.spinygui.core.style.stylesheet](docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/README.md) - 12 direct class(es); CSS stylesheet domain model: properties, rulesets, declarations, terms, specificity, and provider registry. +- [com.spinyowl.spinygui.core.style.stylesheet.annotation](docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/annotation/README.md) - 1 direct class(es); Annotations used by stylesheet property providers. +- [com.spinyowl.spinygui.core.style.stylesheet.atrule](docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/atrule/README.md) - 1 direct class(es); CSS at-rule model objects. +- [com.spinyowl.spinygui.core.style.stylesheet.impl](docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/impl/README.md) - 2 direct class(es); Default property-store implementation and provider scanner integration. +- [com.spinyowl.spinygui.core.style.stylesheet.property](docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/property/README.md) - 21 direct class(es); CSS property providers that parse declarations into typed style values. +- [com.spinyowl.spinygui.core.style.stylesheet.selector](docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/selector/README.md) - 4 direct class(es); Selector contracts and base selector types. +- [com.spinyowl.spinygui.core.style.stylesheet.selector.combinator](docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/selector/combinator/README.md) - 5 direct class(es); Combinator selectors for descendant, child, sibling, adjacent sibling, and compound matching. +- [com.spinyowl.spinygui.core.style.stylesheet.selector.pseudoclass](docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/selector/pseudoclass/README.md) - 1 direct class(es); Pseudo-class selector implementations. +- [com.spinyowl.spinygui.core.style.stylesheet.selector.pseudoelement](docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/selector/pseudoelement/README.md) - 3 direct class(es); Pseudo-element selector implementations. +- [com.spinyowl.spinygui.core.style.stylesheet.selector.simple](docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/selector/simple/README.md) - 4 direct class(es); Simple selectors for all, element, class, and id matching. +- [com.spinyowl.spinygui.core.style.stylesheet.term](docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/term/README.md) - 9 direct class(es); Typed CSS term values produced by parser visitors. +- [com.spinyowl.spinygui.core.style.stylesheet.util](docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/util/README.md) - 1 direct class(es); Utility functions for converting and validating stylesheet values. +- [com.spinyowl.spinygui.core.style.types](docs/project-structure/packages/com/spinyowl/spinygui/core/style/types/README.md) - 10 direct class(es); Typed CSS value objects and constants for non-nested style domains. +- [com.spinyowl.spinygui.core.style.types.background](docs/project-structure/packages/com/spinyowl/spinygui/core/style/types/background/README.md) - 3 direct class(es); Background-origin, repeat, and sizing value objects. +- [com.spinyowl.spinygui.core.style.types.border](docs/project-structure/packages/com/spinyowl/spinygui/core/style/types/border/README.md) - 2 direct class(es); Border item and border-style value objects. +- [com.spinyowl.spinygui.core.style.types.flex](docs/project-structure/packages/com/spinyowl/spinygui/core/style/types/flex/README.md) - 6 direct class(es); Flexbox alignment, direction, wrapping, and justification value constants. +- [com.spinyowl.spinygui.core.style.types.length](docs/project-structure/packages/com/spinyowl/spinygui/core/style/types/length/README.md) - 3 direct class(es); CSS length units, length wrappers, and conversion contract. +- [com.spinyowl.spinygui.core.system](docs/project-structure/packages/com/spinyowl/spinygui/core/system/README.md) - 0 direct class(es); Package for system related classes. +- [com.spinyowl.spinygui.core.system.event](docs/project-structure/packages/com/spinyowl/spinygui/core/system/event/README.md) - 16 direct class(es); Raw platform/window/input events before conversion into application-level events. +- [com.spinyowl.spinygui.core.system.event.listener](docs/project-structure/packages/com/spinyowl/spinygui/core/system/event/listener/README.md) - 27 direct class(es); Adapters that translate raw system events into core event processing and state changes. +- [com.spinyowl.spinygui.core.system.event.processor](docs/project-structure/packages/com/spinyowl/spinygui/core/system/event/processor/README.md) - 2 direct class(es); System-event processor contract and implementation for dispatching platform events. +- [com.spinyowl.spinygui.core.system.event.provider](docs/project-structure/packages/com/spinyowl/spinygui/core/system/event/provider/README.md) - 2 direct class(es); Provider for mapping raw system event classes to listener instances. +- [com.spinyowl.spinygui.core.system.font](docs/project-structure/packages/com/spinyowl/spinygui/core/system/font/README.md) - 7 direct class(es); Platform font loading, text metrics, and font storage abstractions. +- [com.spinyowl.spinygui.core.system.font.impl](docs/project-structure/packages/com/spinyowl/spinygui/core/system/font/impl/README.md) - 3 direct class(es); Default font service, storage, and platform-specific font directory discovery. +- [com.spinyowl.spinygui.core.system.input](docs/project-structure/packages/com/spinyowl/spinygui/core/system/input/README.md) - 3 direct class(es); Platform-facing key, modifier, action, and mouse-button enums. +- [com.spinyowl.spinygui.core.time](docs/project-structure/packages/com/spinyowl/spinygui/core/time/README.md) - 1 direct class(es); Time service abstraction for animation and frame timing. +- [com.spinyowl.spinygui.core.util](docs/project-structure/packages/com/spinyowl/spinygui/core/util/README.md) - 7 direct class(es); Small utilities for class-key maps, IO, node visibility, references, and text handling. +- [com.spinyowl.spinygui.demo](docs/project-structure/packages/com/spinyowl/spinygui/demo/README.md) - 0 direct class(es); Demo namespace folder for runnable examples. +- [com.spinyowl.spinygui.demo.complex](docs/project-structure/packages/com/spinyowl/spinygui/demo/complex/README.md) - 2 direct class(es); Windowed GLFW/LWJGL demo framework and concrete NanoVG demo. +- [com.spinyowl.spinygui.demo.simple](docs/project-structure/packages/com/spinyowl/spinygui/demo/simple/README.md) - 2 direct class(es); Simple demo entry points. + +## Reading Order + +For bottom-up navigation, start with the deepest packages such as `style.stylesheet.selector.*`, `style.stylesheet.term`, `style.types.*`, `system.event.listener`, `layout.impl`, and `backend.renderer.lwjgl.nanovg.util`; then move upward through their parent package documents and finish with this root overview. \ No newline at end of file diff --git a/docs/project-structure/README.md b/docs/project-structure/README.md new file mode 100644 index 00000000..ba16fc86 --- /dev/null +++ b/docs/project-structure/README.md @@ -0,0 +1,70 @@ +# Project Structure Documentation + +This folder mirrors Java package documentation generated from the project source tree. + +- Root overview: [`PROJECT_STRUCTURE.md`](../../PROJECT_STRUCTURE.md) +- Agent style/principles: [`AGENTS_CODE_STYLE.md`](../../AGENTS_CODE_STYLE.md) + +## Packages by Depth + +- [com.spinyowl.spinygui.core.backend.renderer.lwjgl.nanovg.util](packages/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/util/README.md) +- [com.spinyowl.spinygui.core.backend.renderer.lwjgl.nanovg](packages/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/README.md) +- [com.spinyowl.spinygui.core.parser.impl.css.antlr](packages/com/spinyowl/spinygui/core/parser/impl/css/antlr/README.md) +- [com.spinyowl.spinygui.core.parser.impl.css.visitor](packages/com/spinyowl/spinygui/core/parser/impl/css/visitor/README.md) +- [com.spinyowl.spinygui.core.style.stylesheet.selector.combinator](packages/com/spinyowl/spinygui/core/style/stylesheet/selector/combinator/README.md) +- [com.spinyowl.spinygui.core.style.stylesheet.selector.pseudoclass](packages/com/spinyowl/spinygui/core/style/stylesheet/selector/pseudoclass/README.md) +- [com.spinyowl.spinygui.core.style.stylesheet.selector.pseudoelement](packages/com/spinyowl/spinygui/core/style/stylesheet/selector/pseudoelement/README.md) +- [com.spinyowl.spinygui.core.style.stylesheet.selector.simple](packages/com/spinyowl/spinygui/core/style/stylesheet/selector/simple/README.md) +- [com.spinyowl.spinygui.core.backend.renderer.lwjgl](packages/com/spinyowl/spinygui/core/backend/renderer/lwjgl/README.md) +- [com.spinyowl.spinygui.core.parser.impl.css](packages/com/spinyowl/spinygui/core/parser/impl/css/README.md) +- [com.spinyowl.spinygui.core.style.stylesheet.annotation](packages/com/spinyowl/spinygui/core/style/stylesheet/annotation/README.md) +- [com.spinyowl.spinygui.core.style.stylesheet.atrule](packages/com/spinyowl/spinygui/core/style/stylesheet/atrule/README.md) +- [com.spinyowl.spinygui.core.style.stylesheet.impl](packages/com/spinyowl/spinygui/core/style/stylesheet/impl/README.md) +- [com.spinyowl.spinygui.core.style.stylesheet.property](packages/com/spinyowl/spinygui/core/style/stylesheet/property/README.md) +- [com.spinyowl.spinygui.core.style.stylesheet.selector](packages/com/spinyowl/spinygui/core/style/stylesheet/selector/README.md) +- [com.spinyowl.spinygui.core.style.stylesheet.term](packages/com/spinyowl/spinygui/core/style/stylesheet/term/README.md) +- [com.spinyowl.spinygui.core.style.stylesheet.util](packages/com/spinyowl/spinygui/core/style/stylesheet/util/README.md) +- [com.spinyowl.spinygui.core.style.types.background](packages/com/spinyowl/spinygui/core/style/types/background/README.md) +- [com.spinyowl.spinygui.core.style.types.border](packages/com/spinyowl/spinygui/core/style/types/border/README.md) +- [com.spinyowl.spinygui.core.style.types.flex](packages/com/spinyowl/spinygui/core/style/types/flex/README.md) +- [com.spinyowl.spinygui.core.style.types.length](packages/com/spinyowl/spinygui/core/style/types/length/README.md) +- [com.spinyowl.spinygui.core.system.event.listener](packages/com/spinyowl/spinygui/core/system/event/listener/README.md) +- [com.spinyowl.spinygui.core.system.event.processor](packages/com/spinyowl/spinygui/core/system/event/processor/README.md) +- [com.spinyowl.spinygui.core.system.event.provider](packages/com/spinyowl/spinygui/core/system/event/provider/README.md) +- [com.spinyowl.spinygui.core.system.font.impl](packages/com/spinyowl/spinygui/core/system/font/impl/README.md) +- [com.spinyowl.spinygui.core.backend.renderer](packages/com/spinyowl/spinygui/core/backend/renderer/README.md) +- [com.spinyowl.spinygui.core.event.listener](packages/com/spinyowl/spinygui/core/event/listener/README.md) +- [com.spinyowl.spinygui.core.event.processor](packages/com/spinyowl/spinygui/core/event/processor/README.md) +- [com.spinyowl.spinygui.core.input.impl](packages/com/spinyowl/spinygui/core/input/impl/README.md) +- [com.spinyowl.spinygui.core.layout.impl](packages/com/spinyowl/spinygui/core/layout/impl/README.md) +- [com.spinyowl.spinygui.core.node.intersection](packages/com/spinyowl/spinygui/core/node/intersection/README.md) +- [com.spinyowl.spinygui.core.node.layout](packages/com/spinyowl/spinygui/core/node/layout/README.md) +- [com.spinyowl.spinygui.core.parser.impl](packages/com/spinyowl/spinygui/core/parser/impl/README.md) +- [com.spinyowl.spinygui.core.style.manager](packages/com/spinyowl/spinygui/core/style/manager/README.md) +- [com.spinyowl.spinygui.core.style.stylesheet](packages/com/spinyowl/spinygui/core/style/stylesheet/README.md) +- [com.spinyowl.spinygui.core.style.types](packages/com/spinyowl/spinygui/core/style/types/README.md) +- [com.spinyowl.spinygui.core.system.event](packages/com/spinyowl/spinygui/core/system/event/README.md) +- [com.spinyowl.spinygui.core.system.font](packages/com/spinyowl/spinygui/core/system/font/README.md) +- [com.spinyowl.spinygui.core.system.input](packages/com/spinyowl/spinygui/core/system/input/README.md) +- [com.spinyowl.spinygui.core.animation](packages/com/spinyowl/spinygui/core/animation/README.md) +- [com.spinyowl.spinygui.core.backend](packages/com/spinyowl/spinygui/core/backend/README.md) +- [com.spinyowl.spinygui.core.clipboard](packages/com/spinyowl/spinygui/core/clipboard/README.md) +- [com.spinyowl.spinygui.core.cursor](packages/com/spinyowl/spinygui/core/cursor/README.md) +- [com.spinyowl.spinygui.core.event](packages/com/spinyowl/spinygui/core/event/README.md) +- [com.spinyowl.spinygui.core.font](packages/com/spinyowl/spinygui/core/font/README.md) +- [com.spinyowl.spinygui.core.image](packages/com/spinyowl/spinygui/core/image/README.md) +- [com.spinyowl.spinygui.core.input](packages/com/spinyowl/spinygui/core/input/README.md) +- [com.spinyowl.spinygui.core.layout](packages/com/spinyowl/spinygui/core/layout/README.md) +- [com.spinyowl.spinygui.core.node](packages/com/spinyowl/spinygui/core/node/README.md) +- [com.spinyowl.spinygui.core.parser](packages/com/spinyowl/spinygui/core/parser/README.md) +- [com.spinyowl.spinygui.core.style](packages/com/spinyowl/spinygui/core/style/README.md) +- [com.spinyowl.spinygui.core.system](packages/com/spinyowl/spinygui/core/system/README.md) +- [com.spinyowl.spinygui.core.time](packages/com/spinyowl/spinygui/core/time/README.md) +- [com.spinyowl.spinygui.core.util](packages/com/spinyowl/spinygui/core/util/README.md) +- [com.spinyowl.spinygui.demo.complex](packages/com/spinyowl/spinygui/demo/complex/README.md) +- [com.spinyowl.spinygui.demo.simple](packages/com/spinyowl/spinygui/demo/simple/README.md) +- [com.spinyowl.spinygui.core](packages/com/spinyowl/spinygui/core/README.md) +- [com.spinyowl.spinygui.demo](packages/com/spinyowl/spinygui/demo/README.md) +- [com.spinyowl.spinygui](packages/com/spinyowl/spinygui/README.md) +- [com.spinyowl](packages/com/spinyowl/README.md) +- [com](packages/com/README.md) \ No newline at end of file diff --git a/docs/project-structure/packages/com/README.md b/docs/project-structure/packages/com/README.md new file mode 100644 index 00000000..8a8db569 --- /dev/null +++ b/docs/project-structure/packages/com/README.md @@ -0,0 +1,20 @@ +# com + +Top-level Java namespace folder for project packages. + +- Modules: core, core.backend, core.backend.lwjgl.nanovg, demo.complex, demo.simple +- Source sets: main, test +- Direct classes: 0 +- Descendant packages: 60 + +## Classes + +No direct Java classes were found in this package. + +## Child Packages + +- [com.spinyowl](spinyowl/README.md) - SpinyOwl namespace folder. + +## Aggregated Contents + +This package aggregates 60 descendant package(s) with 266 descendant class(es). diff --git a/docs/project-structure/packages/com/spinyowl/README.md b/docs/project-structure/packages/com/spinyowl/README.md new file mode 100644 index 00000000..6861d9e7 --- /dev/null +++ b/docs/project-structure/packages/com/spinyowl/README.md @@ -0,0 +1,20 @@ +# com.spinyowl + +SpinyOwl namespace folder. + +- Modules: core, core.backend, core.backend.lwjgl.nanovg, demo.complex, demo.simple +- Source sets: main, test +- Direct classes: 0 +- Descendant packages: 59 + +## Classes + +No direct Java classes were found in this package. + +## Child Packages + +- [com.spinyowl.spinygui](spinygui/README.md) - SpinyGUI namespace folder aggregating core, backend, and demo packages. + +## Aggregated Contents + +This package aggregates 59 descendant package(s) with 266 descendant class(es). diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/README.md new file mode 100644 index 00000000..4fcbd555 --- /dev/null +++ b/docs/project-structure/packages/com/spinyowl/spinygui/README.md @@ -0,0 +1,21 @@ +# com.spinyowl.spinygui + +SpinyGUI namespace folder aggregating core, backend, and demo packages. + +- Modules: core, core.backend, core.backend.lwjgl.nanovg, demo.complex, demo.simple +- Source sets: main, test +- Direct classes: 0 +- Descendant packages: 58 + +## Classes + +No direct Java classes were found in this package. + +## Child Packages + +- [com.spinyowl.spinygui.core](core/README.md) - Top-level core configuration and shared entry points for the GUI engine. +- [com.spinyowl.spinygui.demo](demo/README.md) - Demo namespace folder for runnable examples. + +## Aggregated Contents + +This package aggregates 58 descendant package(s) with 266 descendant class(es). diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/core/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/core/README.md new file mode 100644 index 00000000..08efc21b --- /dev/null +++ b/docs/project-structure/packages/com/spinyowl/spinygui/core/README.md @@ -0,0 +1,39 @@ +# com.spinyowl.spinygui.core + +Top-level core configuration and shared entry points for the GUI engine. + +- Modules: core, core.backend, core.backend.lwjgl.nanovg +- Source sets: main, test +- Direct classes: 1 +- Descendant packages: 54 + +## Classes + +### Configuration + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/Configuration.java` +- Declaration: `final class Configuration` +- Responsibility: Configuration class that used to define configuration for GUI system. + +## Child Packages + +- [com.spinyowl.spinygui.core.animation](animation/README.md) - Frame-time animation contracts and a simple animator loop. +- [com.spinyowl.spinygui.core.backend](backend/README.md) - Backend namespace folder for renderer APIs and implementations. +- [com.spinyowl.spinygui.core.clipboard](clipboard/README.md) - Clipboard abstraction used by platform integrations. +- [com.spinyowl.spinygui.core.cursor](cursor/README.md) - Cursor model and cursor service abstraction. +- [com.spinyowl.spinygui.core.event](event/README.md) - Application-level events emitted to nodes and event targets. +- [com.spinyowl.spinygui.core.font](font/README.md) - CSS-like font value objects: family, size, stretch, style, and weight. +- [com.spinyowl.spinygui.core.image](image/README.md) - Image abstraction used by style and rendering layers. +- [com.spinyowl.spinygui.core.input](input/README.md) - Input domain model for keyboard, mouse, shortcuts, and user-facing key mappings. +- [com.spinyowl.spinygui.core.layout](layout/README.md) - Layout contracts, layout context, and text/element layout interfaces. +- [com.spinyowl.spinygui.core.node](node/README.md) - DOM-like node hierarchy: frames, elements, empty elements, text nodes, and builders. +- [com.spinyowl.spinygui.core.parser](parser/README.md) - Parser interfaces for HTML-like node trees and stylesheets. +- [com.spinyowl.spinygui.core.style](style/README.md) - Resolved style state applied to nodes after rule matching and property conversion. +- [com.spinyowl.spinygui.core.system](system/README.md) - Package for system related classes. +- [com.spinyowl.spinygui.core.time](time/README.md) - Time service abstraction for animation and frame timing. +- [com.spinyowl.spinygui.core.util](util/README.md) - Small utilities for class-key maps, IO, node visibility, references, and text handling. + +## Aggregated Contents + +This package aggregates 54 descendant package(s) with 261 descendant class(es). diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/core/animation/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/core/animation/README.md new file mode 100644 index 00000000..f10d2e88 --- /dev/null +++ b/docs/project-structure/packages/com/spinyowl/spinygui/core/animation/README.md @@ -0,0 +1,31 @@ +# com.spinyowl.spinygui.core.animation + +Frame-time animation contracts and a simple animator loop. + +- Modules: core +- Source sets: main +- Direct classes: 3 +- Descendant packages: 0 + +## Classes + +### Animation + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/animation/Animation.java` +- Declaration: `abstract class Animation` +- Responsibility: Draft animation realization. + +### Animator + +- Kind: interface +- Source: `core/src/main/java/com/spinyowl/spinygui/core/animation/Animator.java` +- Declaration: `public interface Animator` +- Responsibility: Animation processor. + +### AnimatorImpl + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/animation/AnimatorImpl.java` +- Declaration: `public class AnimatorImpl implements Animator` +- Responsibility: Default implementation of Animator interface. diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/core/backend/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/core/backend/README.md new file mode 100644 index 00000000..f6936e25 --- /dev/null +++ b/docs/project-structure/packages/com/spinyowl/spinygui/core/backend/README.md @@ -0,0 +1,20 @@ +# com.spinyowl.spinygui.core.backend + +Backend namespace folder for renderer APIs and implementations. + +- Modules: core.backend, core.backend.lwjgl.nanovg +- Source sets: main +- Direct classes: 0 +- Descendant packages: 4 + +## Classes + +No direct Java classes were found in this package. + +## Child Packages + +- [com.spinyowl.spinygui.core.backend.renderer](renderer/README.md) - Renderer SPI consumed by backend implementations. + +## Aggregated Contents + +This package aggregates 4 descendant package(s) with 8 descendant class(es). diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/core/backend/renderer/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/core/backend/renderer/README.md new file mode 100644 index 00000000..c80041f0 --- /dev/null +++ b/docs/project-structure/packages/com/spinyowl/spinygui/core/backend/renderer/README.md @@ -0,0 +1,25 @@ +# com.spinyowl.spinygui.core.backend.renderer + +Renderer SPI consumed by backend implementations. + +- Modules: core.backend, core.backend.lwjgl.nanovg +- Source sets: main +- Direct classes: 1 +- Descendant packages: 3 + +## Classes + +### Renderer + +- Kind: interface +- Source: `core.backend/src/main/java/com/spinyowl/spinygui/core/backend/renderer/Renderer.java` +- Declaration: `public interface Renderer` +- Responsibility: Common renderer interface. + +## Child Packages + +- [com.spinyowl.spinygui.core.backend.renderer.lwjgl](lwjgl/README.md) - Package for lwjgl related classes. + +## Aggregated Contents + +This package aggregates 3 descendant package(s) with 7 descendant class(es). diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/core/backend/renderer/lwjgl/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/core/backend/renderer/lwjgl/README.md new file mode 100644 index 00000000..65f4abee --- /dev/null +++ b/docs/project-structure/packages/com/spinyowl/spinygui/core/backend/renderer/lwjgl/README.md @@ -0,0 +1,20 @@ +# com.spinyowl.spinygui.core.backend.renderer.lwjgl + +Package for lwjgl related classes. + +- Modules: core.backend.lwjgl.nanovg +- Source sets: main +- Direct classes: 0 +- Descendant packages: 2 + +## Classes + +No direct Java classes were found in this package. + +## Child Packages + +- [com.spinyowl.spinygui.core.backend.renderer.lwjgl.nanovg](nanovg/README.md) - NanoVG renderer orchestration and specialized element/text/border renderers. + +## Aggregated Contents + +This package aggregates 2 descendant package(s) with 7 descendant class(es). diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/README.md new file mode 100644 index 00000000..c4661883 --- /dev/null +++ b/docs/project-structure/packages/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/README.md @@ -0,0 +1,46 @@ +# com.spinyowl.spinygui.core.backend.renderer.lwjgl.nanovg + +NanoVG renderer orchestration and specialized element/text/border renderers. + +- Modules: core.backend.lwjgl.nanovg +- Source sets: main +- Direct classes: 4 +- Descendant packages: 1 + +## Classes + +### NvgBorderRenderer + +- Kind: class +- Source: `core.backend.lwjgl.nanovg/src/main/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/NvgBorderRenderer.java` +- Declaration: `public class NvgBorderRenderer` +- Responsibility: Represents nvg border renderer in this package. + +### NvgElementRenderer + +- Kind: class +- Source: `core.backend.lwjgl.nanovg/src/main/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/NvgElementRenderer.java` +- Declaration: `public class NvgElementRenderer` +- Responsibility: Represents nvg element renderer in this package. + +### NvgRenderer + +- Kind: class +- Source: `core.backend.lwjgl.nanovg/src/main/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/NvgRenderer.java` +- Declaration: `public class NvgRenderer implements Renderer` +- Responsibility: Represents nvg renderer in this package. + +### NvgTextRenderer + +- Kind: class +- Source: `core.backend.lwjgl.nanovg/src/main/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/NvgTextRenderer.java` +- Declaration: `public class NvgTextRenderer` +- Responsibility: Represents nvg text renderer in this package. + +## Child Packages + +- [com.spinyowl.spinygui.core.backend.renderer.lwjgl.nanovg.util](util/README.md) - NanoVG drawing and color helpers. + +## Aggregated Contents + +This package aggregates 1 descendant package(s) with 3 descendant class(es). diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/util/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/util/README.md new file mode 100644 index 00000000..fae5f6d1 --- /dev/null +++ b/docs/project-structure/packages/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/util/README.md @@ -0,0 +1,31 @@ +# com.spinyowl.spinygui.core.backend.renderer.lwjgl.nanovg.util + +NanoVG drawing and color helpers. + +- Modules: core.backend.lwjgl.nanovg +- Source sets: main +- Direct classes: 3 +- Descendant packages: 0 + +## Classes + +### NvgColorUtil + +- Kind: class +- Source: `core.backend.lwjgl.nanovg/src/main/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/util/NvgColorUtil.java` +- Declaration: `final class NvgColorUtil` +- Responsibility: NanoVG utility. + +### NvgRenderUtils + +- Kind: class +- Source: `core.backend.lwjgl.nanovg/src/main/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/util/NvgRenderUtils.java` +- Declaration: `final class NvgRenderUtils` +- Responsibility: Static helper methods for the named subsystem. + +### NvgShapes + +- Kind: class +- Source: `core.backend.lwjgl.nanovg/src/main/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/util/NvgShapes.java` +- Declaration: `public class NvgShapes` +- Responsibility: NanoVG Shape utility. diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/core/clipboard/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/core/clipboard/README.md new file mode 100644 index 00000000..9022120b --- /dev/null +++ b/docs/project-structure/packages/com/spinyowl/spinygui/core/clipboard/README.md @@ -0,0 +1,17 @@ +# com.spinyowl.spinygui.core.clipboard + +Clipboard abstraction used by platform integrations. + +- Modules: core +- Source sets: main +- Direct classes: 1 +- Descendant packages: 0 + +## Classes + +### Clipboard + +- Kind: interface +- Source: `core/src/main/java/com/spinyowl/spinygui/core/clipboard/Clipboard.java` +- Declaration: `public interface Clipboard` +- Responsibility: Represents clipboard in this package. diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/core/cursor/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/core/cursor/README.md new file mode 100644 index 00000000..438b8aa3 --- /dev/null +++ b/docs/project-structure/packages/com/spinyowl/spinygui/core/cursor/README.md @@ -0,0 +1,31 @@ +# com.spinyowl.spinygui.core.cursor + +Cursor model and cursor service abstraction. + +- Modules: core +- Source sets: main +- Direct classes: 3 +- Descendant packages: 0 + +## Classes + +### Cursor + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/cursor/Cursor.java` +- Declaration: `public class Cursor` +- Responsibility: Represents cursor in this package. + +### CursorMod + +- Kind: enum +- Source: `core/src/main/java/com/spinyowl/spinygui/core/cursor/CursorMod.java` +- Declaration: `public enum CursorMod` +- Responsibility: Enumerates supported values for the named domain concept. + +### CursorService + +- Kind: interface +- Source: `core/src/main/java/com/spinyowl/spinygui/core/cursor/CursorService.java` +- Declaration: `public interface CursorService` +- Responsibility: Service contract for the related subsystem. diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/core/event/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/core/event/README.md new file mode 100644 index 00000000..8272a692 --- /dev/null +++ b/docs/project-structure/packages/com/spinyowl/spinygui/core/event/README.md @@ -0,0 +1,166 @@ +# com.spinyowl.spinygui.core.event + +Application-level events emitted to nodes and event targets. + +- Modules: core +- Source sets: main +- Direct classes: 21 +- Descendant packages: 2 + +## Classes + +### ChangePositionEvent + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/event/ChangePositionEvent.java` +- Declaration: `public class ChangePositionEvent extends Event` +- Responsibility: Event payload object for the named input/window/node change. + +### ChangeSizeEvent + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/event/ChangeSizeEvent.java` +- Declaration: `public class ChangeSizeEvent extends Event` +- Responsibility: Event payload object for the named input/window/node change. + +### ChangeTextEvent + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/event/ChangeTextEvent.java` +- Declaration: `public class ChangeTextEvent extends Event` +- Responsibility: Event payload object for the named input/window/node change. + +### CharEvent + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/event/CharEvent.java` +- Declaration: `public class CharEvent extends Event` +- Responsibility: Event payload object for the named input/window/node change. + +### CursorEnterEvent + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/event/CursorEnterEvent.java` +- Declaration: `public class CursorEnterEvent extends Event` +- Responsibility: Event payload object for the named input/window/node change. + +### CursorExitEvent + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/event/CursorExitEvent.java` +- Declaration: `public class CursorExitEvent extends Event` +- Responsibility: Event payload object for the named input/window/node change. + +### Event + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/event/Event.java` +- Declaration: `public class Event` +- Responsibility: Event payload object for the named input/window/node change. + +### EventTarget + +- Kind: interface +- Source: `core/src/main/java/com/spinyowl/spinygui/core/event/EventTarget.java` +- Declaration: `public interface EventTarget` +- Responsibility: Event target interface. + +### FileDropEvent + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/event/FileDropEvent.java` +- Declaration: `public class FileDropEvent extends Event` +- Responsibility: Event payload object for the named input/window/node change. + +### FocusInEvent + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/event/FocusInEvent.java` +- Declaration: `public class FocusInEvent extends Event` +- Responsibility: Event payload object for the named input/window/node change. + +### FocusOutEvent + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/event/FocusOutEvent.java` +- Declaration: `public class FocusOutEvent extends Event` +- Responsibility: Event payload object for the named input/window/node change. + +### KeyboardEvent + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/event/KeyboardEvent.java` +- Declaration: `public class KeyboardEvent extends Event` +- Responsibility: Event payload object for the named input/window/node change. + +### MouseClickEvent + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/event/MouseClickEvent.java` +- Declaration: `public class MouseClickEvent extends Event` +- Responsibility: Event payload object for the named input/window/node change. + +### MouseDragEvent + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/event/MouseDragEvent.java` +- Declaration: `public class MouseDragEvent extends Event` +- Responsibility: Event payload object for the named input/window/node change. + +### ScrollEvent + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/event/ScrollEvent.java` +- Declaration: `public class ScrollEvent extends Event` +- Responsibility: Event payload object for the named input/window/node change. + +### WindowCloseEvent + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/event/WindowCloseEvent.java` +- Declaration: `public class WindowCloseEvent extends Event` +- Responsibility: Event payload object for the named input/window/node change. + +### WindowFocusEvent + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/event/WindowFocusEvent.java` +- Declaration: `public class WindowFocusEvent extends Event` +- Responsibility: Event payload object for the named input/window/node change. + +### WindowIconifyEvent + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/event/WindowIconifyEvent.java` +- Declaration: `public class WindowIconifyEvent extends Event` +- Responsibility: Event payload object for the named input/window/node change. + +### WindowPosEvent + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/event/WindowPosEvent.java` +- Declaration: `public class WindowPosEvent extends Event` +- Responsibility: Event payload object for the named input/window/node change. + +### WindowRefreshEvent + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/event/WindowRefreshEvent.java` +- Declaration: `public class WindowRefreshEvent extends Event` +- Responsibility: Event payload object for the named input/window/node change. + +### WindowSizeEvent + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/event/WindowSizeEvent.java` +- Declaration: `public class WindowSizeEvent extends Event` +- Responsibility: Event payload object for the named input/window/node change. + +## Child Packages + +- [com.spinyowl.spinygui.core.event.listener](listener/README.md) - Generic event listener contract for application events. +- [com.spinyowl.spinygui.core.event.processor](processor/README.md) - Dispatch logic for routing application events to node listeners. + +## Aggregated Contents + +This package aggregates 2 descendant package(s) with 3 descendant class(es). diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/core/event/listener/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/core/event/listener/README.md new file mode 100644 index 00000000..8f6e6378 --- /dev/null +++ b/docs/project-structure/packages/com/spinyowl/spinygui/core/event/listener/README.md @@ -0,0 +1,17 @@ +# com.spinyowl.spinygui.core.event.listener + +Generic event listener contract for application events. + +- Modules: core +- Source sets: main +- Direct classes: 1 +- Descendant packages: 0 + +## Classes + +### EventListener + +- Kind: interface +- Source: `core/src/main/java/com/spinyowl/spinygui/core/event/listener/EventListener.java` +- Declaration: `public interface EventListener` +- Responsibility: System-event listener/adapter for translating platform events into core behavior. diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/core/event/processor/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/core/event/processor/README.md new file mode 100644 index 00000000..a5dade27 --- /dev/null +++ b/docs/project-structure/packages/com/spinyowl/spinygui/core/event/processor/README.md @@ -0,0 +1,24 @@ +# com.spinyowl.spinygui.core.event.processor + +Dispatch logic for routing application events to node listeners. + +- Modules: core +- Source sets: main +- Direct classes: 2 +- Descendant packages: 0 + +## Classes + +### DefaultEventProcessor + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/event/processor/DefaultEventProcessor.java` +- Declaration: `public class DefaultEventProcessor implements EventProcessor` +- Responsibility: Represents default event processor in this package. + +### EventProcessor + +- Kind: interface +- Source: `core/src/main/java/com/spinyowl/spinygui/core/event/processor/EventProcessor.java` +- Declaration: `public interface EventProcessor` +- Responsibility: Represents event processor in this package. diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/core/font/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/core/font/README.md new file mode 100644 index 00000000..96e04ea2 --- /dev/null +++ b/docs/project-structure/packages/com/spinyowl/spinygui/core/font/README.md @@ -0,0 +1,45 @@ +# com.spinyowl.spinygui.core.font + +CSS-like font value objects: family, size, stretch, style, and weight. + +- Modules: core +- Source sets: main +- Direct classes: 5 +- Descendant packages: 0 + +## Classes + +### Font + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/font/Font.java` +- Declaration: `public class Font` +- Responsibility: Represents font in this package. + +### FontSize + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/font/FontSize.java` +- Declaration: `public class FontSize` +- Responsibility: Represents font size in this package. + +### FontStretch + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/font/FontStretch.java` +- Declaration: `final class FontStretch` +- Responsibility: CSS font stretch. + +### FontStyle + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/font/FontStyle.java` +- Declaration: `final class FontStyle` +- Responsibility: Represents font style in this package. + +### FontWeight + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/font/FontWeight.java` +- Declaration: `final class FontWeight` +- Responsibility: CSS font weight. diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/core/image/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/core/image/README.md new file mode 100644 index 00000000..c323c703 --- /dev/null +++ b/docs/project-structure/packages/com/spinyowl/spinygui/core/image/README.md @@ -0,0 +1,17 @@ +# com.spinyowl.spinygui.core.image + +Image abstraction used by style and rendering layers. + +- Modules: core +- Source sets: main +- Direct classes: 1 +- Descendant packages: 0 + +## Classes + +### Image + +- Kind: interface +- Source: `core/src/main/java/com/spinyowl/spinygui/core/image/Image.java` +- Declaration: `public interface Image` +- Responsibility: Represents image in this package. diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/core/input/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/core/input/README.md new file mode 100644 index 00000000..be94285a --- /dev/null +++ b/docs/project-structure/packages/com/spinyowl/spinygui/core/input/README.md @@ -0,0 +1,88 @@ +# com.spinyowl.spinygui.core.input + +Input domain model for keyboard, mouse, shortcuts, and user-facing key mappings. + +- Modules: core +- Source sets: main +- Direct classes: 10 +- Descendant packages: 1 + +## Classes + +### KeyAction + +- Kind: enum +- Source: `core/src/main/java/com/spinyowl/spinygui/core/input/KeyAction.java` +- Declaration: `public enum KeyAction` +- Responsibility: Enumerates supported values for the named domain concept. + +### Keyboard + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/input/Keyboard.java` +- Declaration: `public class Keyboard` +- Responsibility: Represents keyboard in this package. + +### KeyboardKey + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/input/KeyboardKey.java` +- Declaration: `public class KeyboardKey` +- Responsibility: Represents keyboard key in this package. + +### KeyboardLayout + +- Kind: interface +- Source: `core/src/main/java/com/spinyowl/spinygui/core/input/KeyboardLayout.java` +- Declaration: `public interface KeyboardLayout` +- Responsibility: Used to store key mapping to native keys. + +### KeyCode + +- Kind: enum +- Source: `core/src/main/java/com/spinyowl/spinygui/core/input/KeyCode.java` +- Declaration: `public enum KeyCode` +- Responsibility: Key code is code value of the physical key represented by the event. + +### KeyMod + +- Kind: enum +- Source: `core/src/main/java/com/spinyowl/spinygui/core/input/KeyMod.java` +- Declaration: `public enum KeyMod` +- Responsibility: Enumerates supported values for the named domain concept. + +### MouseButton + +- Kind: enum +- Source: `core/src/main/java/com/spinyowl/spinygui/core/input/MouseButton.java` +- Declaration: `public enum MouseButton` +- Responsibility: Enumerates supported values for the named domain concept. + +### MouseService + +- Kind: interface +- Source: `core/src/main/java/com/spinyowl/spinygui/core/input/MouseService.java` +- Declaration: `public interface MouseService` +- Responsibility: Provides ability to get and set cursor positions. + +### Shortcut + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/input/Shortcut.java` +- Declaration: `public class Shortcut` +- Responsibility: Represents shortcut in this package. + +### ShortcutRegistry + +- Kind: interface +- Source: `core/src/main/java/com/spinyowl/spinygui/core/input/ShortcutRegistry.java` +- Declaration: `public interface ShortcutRegistry` +- Responsibility: Used to store shortcuts. + +## Child Packages + +- [com.spinyowl.spinygui.core.input.impl](impl/README.md) - Default mutable implementations of input services. + +## Aggregated Contents + +This package aggregates 1 descendant package(s) with 3 descendant class(es). diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/core/input/impl/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/core/input/impl/README.md new file mode 100644 index 00000000..74f72d98 --- /dev/null +++ b/docs/project-structure/packages/com/spinyowl/spinygui/core/input/impl/README.md @@ -0,0 +1,31 @@ +# com.spinyowl.spinygui.core.input.impl + +Default mutable implementations of input services. + +- Modules: core +- Source sets: main +- Direct classes: 3 +- Descendant packages: 0 + +## Classes + +### KeyboardLayoutImpl + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/input/impl/KeyboardLayoutImpl.java` +- Declaration: `public class KeyboardLayoutImpl implements KeyboardLayout` +- Responsibility: This class used to store key mapping to native keys. + +### MouseServiceImpl + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/input/impl/MouseServiceImpl.java` +- Declaration: `public class MouseServiceImpl implements MouseService` +- Responsibility: Default implementation of MouseService. + +### ShortcutRegistryImpl + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/input/impl/ShortcutRegistryImpl.java` +- Declaration: `public class ShortcutRegistryImpl implements ShortcutRegistry` +- Responsibility: Used to store shortcuts. diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/core/layout/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/core/layout/README.md new file mode 100644 index 00000000..b99f026b --- /dev/null +++ b/docs/project-structure/packages/com/spinyowl/spinygui/core/layout/README.md @@ -0,0 +1,53 @@ +# com.spinyowl.spinygui.core.layout + +Layout contracts, layout context, and text/element layout interfaces. + +- Modules: core +- Source sets: main +- Direct classes: 5 +- Descendant packages: 1 + +## Classes + +### ElementLayout + +- Kind: interface +- Source: `core/src/main/java/com/spinyowl/spinygui/core/layout/ElementLayout.java` +- Declaration: `public interface ElementLayout extends Layout` +- Responsibility: Defines branch for element node layout implementations. + +### Layout + +- Kind: interface +- Source: `core/src/main/java/com/spinyowl/spinygui/core/layout/Layout.java` +- Declaration: `public interface Layout` +- Responsibility: Layout manager. + +### LayoutContext + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/layout/LayoutContext.java` +- Declaration: `public class LayoutContext` +- Responsibility: Represents layout context in this package. + +### LayoutService + +- Kind: interface +- Source: `core/src/main/java/com/spinyowl/spinygui/core/layout/LayoutService.java` +- Declaration: `public interface LayoutService` +- Responsibility: Layout service is an entry point to layout system. + +### TextLayout + +- Kind: interface +- Source: `core/src/main/java/com/spinyowl/spinygui/core/layout/TextLayout.java` +- Declaration: `public interface TextLayout extends Layout` +- Responsibility: Defines branch for text node layout implementations. + +## Child Packages + +- [com.spinyowl.spinygui.core.layout.impl](impl/README.md) - Concrete layout algorithms and utilities for block, flex, none, text, and layout tree updates. + +## Aggregated Contents + +This package aggregates 1 descendant package(s) with 7 descendant class(es). diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/core/layout/impl/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/core/layout/impl/README.md new file mode 100644 index 00000000..8b2ce942 --- /dev/null +++ b/docs/project-structure/packages/com/spinyowl/spinygui/core/layout/impl/README.md @@ -0,0 +1,59 @@ +# com.spinyowl.spinygui.core.layout.impl + +Concrete layout algorithms and utilities for block, flex, none, text, and layout tree updates. + +- Modules: core +- Source sets: main +- Direct classes: 7 +- Descendant packages: 0 + +## Classes + +### BlockLayout + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/layout/impl/BlockLayout.java` +- Declaration: `public class BlockLayout implements ElementLayout` +- Responsibility: Layout algorithm or layout contract for the named display/text mode. + +### FlexLayout + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/layout/impl/FlexLayout.java` +- Declaration: `public class FlexLayout implements ElementLayout` +- Responsibility: Layout algorithm or layout contract for the named display/text mode. + +### LayoutServiceImpl + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/layout/impl/LayoutServiceImpl.java` +- Declaration: `public class LayoutServiceImpl implements LayoutService` +- Responsibility: Layout service is an entry point to layout system. + +### LayoutServiceProvider + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/layout/impl/LayoutServiceProvider.java` +- Declaration: `final class LayoutServiceProvider` +- Responsibility: Provider implementation or registry for constructing subsystem collaborators. + +### LayoutUtils + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/layout/impl/LayoutUtils.java` +- Declaration: `final class LayoutUtils` +- Responsibility: Static helper methods for the named subsystem. + +### NoneLayout + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/layout/impl/NoneLayout.java` +- Declaration: `public class NoneLayout implements ElementLayout` +- Responsibility: Layout algorithm or layout contract for the named display/text mode. + +### TextLayoutImpl + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/layout/impl/TextLayoutImpl.java` +- Declaration: `public class TextLayoutImpl implements TextLayout` +- Responsibility: Default implementation of the matching interface. diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/core/node/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/core/node/README.md new file mode 100644 index 00000000..a1ca0d18 --- /dev/null +++ b/docs/project-structure/packages/com/spinyowl/spinygui/core/node/README.md @@ -0,0 +1,61 @@ +# com.spinyowl.spinygui.core.node + +DOM-like node hierarchy: frames, elements, empty elements, text nodes, and builders. + +- Modules: core +- Source sets: main +- Direct classes: 6 +- Descendant packages: 2 + +## Classes + +### Element + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/node/Element.java` +- Declaration: `public class Element extends Node implements EventTarget` +- Responsibility: Represents element in this package. + +### EmptyElement + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/node/EmptyElement.java` +- Declaration: `public class EmptyElement extends Element` +- Responsibility: Defines node that can not contain child elements. + +### Frame + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/node/Frame.java` +- Declaration: `public class Frame extends Element` +- Responsibility: The root element that holds all stylesheets and other nodes. + +### Node + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/node/Node.java` +- Declaration: `abstract class Node` +- Responsibility: Base structure of any node. + +### NodeBuilder + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/node/NodeBuilder.java` +- Declaration: `final class NodeBuilder` +- Responsibility: Represents node builder in this package. + +### Text + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/node/Text.java` +- Declaration: `final class Text extends Node` +- Responsibility: Represents text in this package. + +## Child Packages + +- [com.spinyowl.spinygui.core.node.intersection](intersection/README.md) - Hit-testing strategy objects for node intersection checks. +- [com.spinyowl.spinygui.core.node.layout](layout/README.md) - Box-model geometry value objects used by layout and rendering. + +## Aggregated Contents + +This package aggregates 2 descendant package(s) with 6 descendant class(es). diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/core/node/intersection/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/core/node/intersection/README.md new file mode 100644 index 00000000..2d60fb9d --- /dev/null +++ b/docs/project-structure/packages/com/spinyowl/spinygui/core/node/intersection/README.md @@ -0,0 +1,31 @@ +# com.spinyowl.spinygui.core.node.intersection + +Hit-testing strategy objects for node intersection checks. + +- Modules: core +- Source sets: main +- Direct classes: 3 +- Descendant packages: 0 + +## Classes + +### Intersection + +- Kind: interface +- Source: `core/src/main/java/com/spinyowl/spinygui/core/node/intersection/Intersection.java` +- Declaration: `public interface Intersection` +- Responsibility: Intersection class specifies intersection rules for node and point. + +### Intersections + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/node/intersection/Intersections.java` +- Declaration: `final class Intersections` +- Responsibility: Represents intersections in this package. + +### RectangleIntersection + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/node/intersection/RectangleIntersection.java` +- Declaration: `public class RectangleIntersection implements Intersection` +- Responsibility: Represents rectangle intersection in this package. diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/core/node/layout/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/core/node/layout/README.md new file mode 100644 index 00000000..d20be27a --- /dev/null +++ b/docs/project-structure/packages/com/spinyowl/spinygui/core/node/layout/README.md @@ -0,0 +1,31 @@ +# com.spinyowl.spinygui.core.node.layout + +Box-model geometry value objects used by layout and rendering. + +- Modules: core +- Source sets: main +- Direct classes: 3 +- Descendant packages: 0 + +## Classes + +### Box + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/node/layout/Box.java` +- Declaration: `final class Box` +- Responsibility: Represents box in this package. + +### Edges + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/node/layout/Edges.java` +- Declaration: `final class Edges` +- Responsibility: Represents edges in this package. + +### Rect + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/node/layout/Rect.java` +- Declaration: `final class Rect` +- Responsibility: Represents rect in this package. diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/core/parser/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/core/parser/README.md new file mode 100644 index 00000000..e39da4e4 --- /dev/null +++ b/docs/project-structure/packages/com/spinyowl/spinygui/core/parser/README.md @@ -0,0 +1,32 @@ +# com.spinyowl.spinygui.core.parser + +Parser interfaces for HTML-like node trees and stylesheets. + +- Modules: core +- Source sets: main +- Direct classes: 2 +- Descendant packages: 4 + +## Classes + +### NodeParser + +- Kind: interface +- Source: `core/src/main/java/com/spinyowl/spinygui/core/parser/NodeParser.java` +- Declaration: `public interface NodeParser` +- Responsibility: Node marshaller. + +### StyleSheetParser + +- Kind: interface +- Source: `core/src/main/java/com/spinyowl/spinygui/core/parser/StyleSheetParser.java` +- Declaration: `public interface StyleSheetParser` +- Responsibility: Used to read stylesheets from css. + +## Child Packages + +- [com.spinyowl.spinygui.core.parser.impl](impl/README.md) - Default parser implementations and parser factory code. + +## Aggregated Contents + +This package aggregates 4 descendant package(s) with 18 descendant class(es). diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/core/parser/impl/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/core/parser/impl/README.md new file mode 100644 index 00000000..c936e200 --- /dev/null +++ b/docs/project-structure/packages/com/spinyowl/spinygui/core/parser/impl/README.md @@ -0,0 +1,46 @@ +# com.spinyowl.spinygui.core.parser.impl + +Default parser implementations and parser factory code. + +- Modules: core +- Source sets: main +- Direct classes: 4 +- Descendant packages: 3 + +## Classes + +### DefaultNodeParser + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/parser/impl/DefaultNodeParser.java` +- Declaration: `public class DefaultNodeParser implements NodeParser` +- Responsibility: Represents default node parser in this package. + +### DefaultStyleSheetParser + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/parser/impl/DefaultStyleSheetParser.java` +- Declaration: `final class DefaultStyleSheetParser implements StyleSheetParser` +- Responsibility: Used to read stylesheets from css. + +### ParseException + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/parser/impl/ParseException.java` +- Declaration: `public class ParseException extends RuntimeException` +- Responsibility: Thrown when there is some node conversion exception happens. + +### StyleSheetParserFactory + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/parser/impl/StyleSheetParserFactory.java` +- Declaration: `final class StyleSheetParserFactory` +- Responsibility: Represents style sheet parser factory in this package. + +## Child Packages + +- [com.spinyowl.spinygui.core.parser.impl.css](css/README.md) - CSS parser namespace containing generated ANTLR artifacts and handwritten semantic visitors. + +## Aggregated Contents + +This package aggregates 3 descendant package(s) with 14 descendant class(es). diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/core/parser/impl/css/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/core/parser/impl/css/README.md new file mode 100644 index 00000000..fd15e3b6 --- /dev/null +++ b/docs/project-structure/packages/com/spinyowl/spinygui/core/parser/impl/css/README.md @@ -0,0 +1,21 @@ +# com.spinyowl.spinygui.core.parser.impl.css + +CSS parser namespace containing generated ANTLR artifacts and handwritten semantic visitors. + +- Modules: core +- Source sets: main +- Direct classes: 0 +- Descendant packages: 2 + +## Classes + +No direct Java classes were found in this package. + +## Child Packages + +- [com.spinyowl.spinygui.core.parser.impl.css.antlr](antlr/README.md) - Generated ANTLR CSS3 lexer/parser/listener/visitor artifacts. Regenerate from the grammar instead of hand-editing. +- [com.spinyowl.spinygui.core.parser.impl.css.visitor](visitor/README.md) - ANTLR visitors that convert CSS parse trees into stylesheet, selector, declaration, and term model objects. + +## Aggregated Contents + +This package aggregates 2 descendant package(s) with 14 descendant class(es). diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/core/parser/impl/css/antlr/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/core/parser/impl/css/antlr/README.md new file mode 100644 index 00000000..c55accad --- /dev/null +++ b/docs/project-structure/packages/com/spinyowl/spinygui/core/parser/impl/css/antlr/README.md @@ -0,0 +1,52 @@ +# com.spinyowl.spinygui.core.parser.impl.css.antlr + +Generated ANTLR CSS3 lexer/parser/listener/visitor artifacts. Regenerate from the grammar instead of hand-editing. + +- Modules: core +- Source sets: main +- Direct classes: 6 +- Descendant packages: 0 + +## Classes + +### CSS3BaseListener + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3BaseListener.java` +- Declaration: `public class CSS3BaseListener implements CSS3Listener` +- Responsibility: This class provides an empty implementation of CSS3Listener, which can be extended to create a listener which only needs to handle a subset of the available methods. + +### CSS3BaseVisitor + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3BaseVisitor.java` +- Declaration: `public class CSS3BaseVisitor extends AbstractParseTreeVisitor implements CSS3Visitor` +- Responsibility: This class provides an empty implementation of CSS3Visitor, which can be extended to create a visitor which only needs to handle a subset of the available methods. + +### CSS3Lexer + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3Lexer.java` +- Declaration: `public class CSS3Lexer extends Lexer` +- Responsibility: Generated ANTLR CSS3 parser artifact; regenerate it from CSS3.g4 rather than editing by hand. + +### CSS3Listener + +- Kind: interface +- Source: `core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3Listener.java` +- Declaration: `public interface CSS3Listener extends ParseTreeListener` +- Responsibility: This interface defines a complete listener for a parse tree produced by CSS3Parser. + +### CSS3Parser + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3Parser.java` +- Declaration: `public class CSS3Parser extends Parser` +- Responsibility: Generated ANTLR CSS3 parser artifact; regenerate it from CSS3.g4 rather than editing by hand. + +### CSS3Visitor + +- Kind: interface +- Source: `core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3Visitor.java` +- Declaration: `public interface CSS3Visitor extends ParseTreeVisitor` +- Responsibility: This interface defines a complete generic visitor for a parse tree produced by CSS3Parser. diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/core/parser/impl/css/visitor/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/core/parser/impl/css/visitor/README.md new file mode 100644 index 00000000..d6b041ea --- /dev/null +++ b/docs/project-structure/packages/com/spinyowl/spinygui/core/parser/impl/css/visitor/README.md @@ -0,0 +1,66 @@ +# com.spinyowl.spinygui.core.parser.impl.css.visitor + +ANTLR visitors that convert CSS parse trees into stylesheet, selector, declaration, and term model objects. + +- Modules: core +- Source sets: main +- Direct classes: 8 +- Descendant packages: 0 + +## Classes + +### AtRuleVisitor + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/visitor/AtRuleVisitor.java` +- Declaration: `public class AtRuleVisitor extends CSS3BaseVisitor` +- Responsibility: ANTLR visitor that maps parse-tree nodes into the stylesheet domain model. + +### DeclarationListVisitor + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/visitor/DeclarationListVisitor.java` +- Declaration: `public class DeclarationListVisitor extends CSS3BaseVisitor>` +- Responsibility: ANTLR visitor that maps parse-tree nodes into the stylesheet domain model. + +### DeclarationVisitor + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/visitor/DeclarationVisitor.java` +- Declaration: `public class DeclarationVisitor extends CSS3BaseVisitor` +- Responsibility: ANTLR visitor that maps parse-tree nodes into the stylesheet domain model. + +### PropertyValueVisitor + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/visitor/PropertyValueVisitor.java` +- Declaration: `public class PropertyValueVisitor extends CSS3BaseVisitor>` +- Responsibility: ANTLR visitor that maps parse-tree nodes into the stylesheet domain model. + +### RulesetVisitor + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/visitor/RulesetVisitor.java` +- Declaration: `public class RulesetVisitor extends CSS3BaseVisitor` +- Responsibility: ANTLR visitor that maps parse-tree nodes into the stylesheet domain model. + +### SelectorGroupVisitor + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/visitor/SelectorGroupVisitor.java` +- Declaration: `public class SelectorGroupVisitor extends CSS3BaseVisitor>` +- Responsibility: ANTLR visitor that maps parse-tree nodes into the stylesheet domain model. + +### SelectorVisitor + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/visitor/SelectorVisitor.java` +- Declaration: `public class SelectorVisitor extends CSS3BaseVisitor` +- Responsibility: ANTLR visitor that maps parse-tree nodes into the stylesheet domain model. + +### StyleSheetVisitor + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/visitor/StyleSheetVisitor.java` +- Declaration: `public class StyleSheetVisitor extends CSS3BaseVisitor` +- Responsibility: ANTLR visitor that maps parse-tree nodes into the stylesheet domain model. diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/core/style/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/core/style/README.md new file mode 100644 index 00000000..9dfe966f --- /dev/null +++ b/docs/project-structure/packages/com/spinyowl/spinygui/core/style/README.md @@ -0,0 +1,27 @@ +# com.spinyowl.spinygui.core.style + +Resolved style state applied to nodes after rule matching and property conversion. + +- Modules: core +- Source sets: main +- Direct classes: 1 +- Descendant packages: 18 + +## Classes + +### ResolvedStyle + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/ResolvedStyle.java` +- Declaration: `public class ResolvedStyle` +- Responsibility: Represents resolved style in this package. + +## Child Packages + +- [com.spinyowl.spinygui.core.style.manager](manager/README.md) - Style manager contract and implementation for applying stylesheets to node trees. +- [com.spinyowl.spinygui.core.style.stylesheet](stylesheet/README.md) - CSS stylesheet domain model: properties, rulesets, declarations, terms, specificity, and provider registry. +- [com.spinyowl.spinygui.core.style.types](types/README.md) - Typed CSS value objects and constants for non-nested style domains. + +## Aggregated Contents + +This package aggregates 18 descendant package(s) with 90 descendant class(es). diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/core/style/manager/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/core/style/manager/README.md new file mode 100644 index 00000000..3926ebd2 --- /dev/null +++ b/docs/project-structure/packages/com/spinyowl/spinygui/core/style/manager/README.md @@ -0,0 +1,24 @@ +# com.spinyowl.spinygui.core.style.manager + +Style manager contract and implementation for applying stylesheets to node trees. + +- Modules: core +- Source sets: main +- Direct classes: 2 +- Descendant packages: 0 + +## Classes + +### StyleManager + +- Kind: interface +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/manager/StyleManager.java` +- Declaration: `public interface StyleManager` +- Responsibility: Represents style manager in this package. + +### StyleManagerImpl + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/manager/StyleManagerImpl.java` +- Declaration: `public class StyleManagerImpl implements StyleManager` +- Responsibility: Default implementation of the matching interface. diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/README.md new file mode 100644 index 00000000..3be4b1f5 --- /dev/null +++ b/docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/README.md @@ -0,0 +1,108 @@ +# com.spinyowl.spinygui.core.style.stylesheet + +CSS stylesheet domain model: properties, rulesets, declarations, terms, specificity, and provider registry. + +- Modules: core +- Source sets: main +- Direct classes: 12 +- Descendant packages: 11 + +## Classes + +### AtRule + +- Kind: interface +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/AtRule.java` +- Declaration: `public interface AtRule` +- Responsibility: Represents at rule in this package. + +### Declaration + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/Declaration.java` +- Declaration: `public class Declaration` +- Responsibility: Declaration is combination of property and it's value. + +### Properties + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/Properties.java` +- Declaration: `final class Properties` +- Responsibility: Represents properties in this package. + +### PropertiesScanner + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/PropertiesScanner.java` +- Declaration: `final class PropertiesScanner` +- Responsibility: Represents properties scanner in this package. + +### Property + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/Property.java` +- Declaration: `public class Property` +- Responsibility: Root class that describes property. + +### PropertyProvider + +- Kind: interface +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/PropertyProvider.java` +- Declaration: `public interface PropertyProvider` +- Responsibility: Provider contract for supplying subsystem collaborators. + +### PropertyStore + +- Kind: interface +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/PropertyStore.java` +- Declaration: `public interface PropertyStore` +- Responsibility: Represents property store in this package. + +### PropertyStoreProvider + +- Kind: interface +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/PropertyStoreProvider.java` +- Declaration: `public interface PropertyStoreProvider` +- Responsibility: Provider contract for supplying subsystem collaborators. + +### Ruleset + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/Ruleset.java` +- Declaration: `public class Ruleset` +- Responsibility: Combines set of selectors and set of declarations which should be applied for elements accessed by those selectors. + +### Specificity + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/Specificity.java` +- Declaration: `public class Specificity implements Comparable` +- Responsibility: Represents specificity in this package. + +### StyleSheet + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/StyleSheet.java` +- Declaration: `public class StyleSheet` +- Responsibility: Represents style sheet in this package. + +### Term + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/Term.java` +- Declaration: `abstract class Term` +- Responsibility: Term is a part of a property value. + +## Child Packages + +- [com.spinyowl.spinygui.core.style.stylesheet.annotation](annotation/README.md) - Annotations used by stylesheet property providers. +- [com.spinyowl.spinygui.core.style.stylesheet.atrule](atrule/README.md) - CSS at-rule model objects. +- [com.spinyowl.spinygui.core.style.stylesheet.impl](impl/README.md) - Default property-store implementation and provider scanner integration. +- [com.spinyowl.spinygui.core.style.stylesheet.property](property/README.md) - CSS property providers that parse declarations into typed style values. +- [com.spinyowl.spinygui.core.style.stylesheet.selector](selector/README.md) - Selector contracts and base selector types. +- [com.spinyowl.spinygui.core.style.stylesheet.term](term/README.md) - Typed CSS term values produced by parser visitors. +- [com.spinyowl.spinygui.core.style.stylesheet.util](util/README.md) - Utility functions for converting and validating stylesheet values. + +## Aggregated Contents + +This package aggregates 11 descendant package(s) with 52 descendant class(es). diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/annotation/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/annotation/README.md new file mode 100644 index 00000000..eef4dd2f --- /dev/null +++ b/docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/annotation/README.md @@ -0,0 +1,17 @@ +# com.spinyowl.spinygui.core.style.stylesheet.annotation + +Annotations used by stylesheet property providers. + +- Modules: core +- Source sets: main +- Direct classes: 1 +- Descendant packages: 0 + +## Classes + +### Priority + +- Kind: annotation +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/annotation/Priority.java` +- Declaration: `public @interface Priority` +- Responsibility: Used to mark property provider to add properties to property store with higher priority. diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/atrule/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/atrule/README.md new file mode 100644 index 00000000..5944c9ab --- /dev/null +++ b/docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/atrule/README.md @@ -0,0 +1,17 @@ +# com.spinyowl.spinygui.core.style.stylesheet.atrule + +CSS at-rule model objects. + +- Modules: core +- Source sets: main +- Direct classes: 1 +- Descendant packages: 0 + +## Classes + +### FontFaceRule + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/atrule/FontFaceRule.java` +- Declaration: `public class FontFaceRule implements AtRule` +- Responsibility: Font face at-rule. diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/impl/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/impl/README.md new file mode 100644 index 00000000..fec52f80 --- /dev/null +++ b/docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/impl/README.md @@ -0,0 +1,24 @@ +# com.spinyowl.spinygui.core.style.stylesheet.impl + +Default property-store implementation and provider scanner integration. + +- Modules: core +- Source sets: main +- Direct classes: 2 +- Descendant packages: 0 + +## Classes + +### DefaultPropertyStore + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/impl/DefaultPropertyStore.java` +- Declaration: `public class DefaultPropertyStore implements PropertyStore` +- Responsibility: Represents default property store in this package. + +### DefaultPropertyStoreProvider + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/impl/DefaultPropertyStoreProvider.java` +- Declaration: `final class DefaultPropertyStoreProvider implements PropertyStoreProvider` +- Responsibility: Provider implementation or registry for constructing subsystem collaborators. diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/property/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/property/README.md new file mode 100644 index 00000000..6defe0ac --- /dev/null +++ b/docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/property/README.md @@ -0,0 +1,157 @@ +# com.spinyowl.spinygui.core.style.stylesheet.property + +CSS property providers that parse declarations into typed style values. + +- Modules: core +- Source sets: main +- Direct classes: 21 +- Descendant packages: 0 + +## Classes + +### BackgroundPropertyProvider + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/BackgroundPropertyProvider.java` +- Declaration: `public class BackgroundPropertyProvider implements PropertyProvider` +- Responsibility: Provider implementation or registry for constructing subsystem collaborators. + +### BorderColorPropertyProvider + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/BorderColorPropertyProvider.java` +- Declaration: `public class BorderColorPropertyProvider implements PropertyProvider` +- Responsibility: Provider implementation or registry for constructing subsystem collaborators. + +### BorderPropertyProvider + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/BorderPropertyProvider.java` +- Declaration: `public class BorderPropertyProvider implements PropertyProvider` +- Responsibility: Provider implementation or registry for constructing subsystem collaborators. + +### BorderRadiusPropertyProvider + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/BorderRadiusPropertyProvider.java` +- Declaration: `public class BorderRadiusPropertyProvider implements PropertyProvider` +- Responsibility: Provider implementation or registry for constructing subsystem collaborators. + +### BorderStylePropertyProvider + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/BorderStylePropertyProvider.java` +- Declaration: `public class BorderStylePropertyProvider implements PropertyProvider` +- Responsibility: Provider implementation or registry for constructing subsystem collaborators. + +### BorderWidthPropertyProvider + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/BorderWidthPropertyProvider.java` +- Declaration: `public class BorderWidthPropertyProvider implements PropertyProvider` +- Responsibility: Provider implementation or registry for constructing subsystem collaborators. + +### BoxShadowPropertyProvider + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/BoxShadowPropertyProvider.java` +- Declaration: `public class BoxShadowPropertyProvider implements PropertyProvider` +- Responsibility: Provider implementation or registry for constructing subsystem collaborators. + +### ColorPropertyProvider + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/ColorPropertyProvider.java` +- Declaration: `public class ColorPropertyProvider implements PropertyProvider` +- Responsibility: Provider implementation or registry for constructing subsystem collaborators. + +### DimensionPropertyProvider + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/DimensionPropertyProvider.java` +- Declaration: `public class DimensionPropertyProvider implements PropertyProvider` +- Responsibility: Provider implementation or registry for constructing subsystem collaborators. + +### DisplayPropertyProvider + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/DisplayPropertyProvider.java` +- Declaration: `public class DisplayPropertyProvider implements PropertyProvider` +- Responsibility: Provider implementation or registry for constructing subsystem collaborators. + +### FlexPropertyProvider + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/FlexPropertyProvider.java` +- Declaration: `public class FlexPropertyProvider implements PropertyProvider` +- Responsibility: Provider implementation or registry for constructing subsystem collaborators. + +### FontPropertyProvider + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/FontPropertyProvider.java` +- Declaration: `public class FontPropertyProvider implements PropertyProvider` +- Responsibility: Provider implementation or registry for constructing subsystem collaborators. + +### MarginPropertyProvider + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/MarginPropertyProvider.java` +- Declaration: `public class MarginPropertyProvider implements PropertyProvider` +- Responsibility: Provider implementation or registry for constructing subsystem collaborators. + +### OpacityPropertyProvider + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/OpacityPropertyProvider.java` +- Declaration: `public class OpacityPropertyProvider implements PropertyProvider` +- Responsibility: Provider implementation or registry for constructing subsystem collaborators. + +### PaddingPropertyProvider + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/PaddingPropertyProvider.java` +- Declaration: `public class PaddingPropertyProvider implements PropertyProvider` +- Responsibility: Provider implementation or registry for constructing subsystem collaborators. + +### PointerEventsPropertyProvider + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/PointerEventsPropertyProvider.java` +- Declaration: `public class PointerEventsPropertyProvider implements PropertyProvider` +- Responsibility: Provider implementation or registry for constructing subsystem collaborators. + +### PositioningPropertyProvider + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/PositioningPropertyProvider.java` +- Declaration: `public class PositioningPropertyProvider implements PropertyProvider` +- Responsibility: Provider implementation or registry for constructing subsystem collaborators. + +### PositionPropertyProvider + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/PositionPropertyProvider.java` +- Declaration: `public class PositionPropertyProvider implements PropertyProvider` +- Responsibility: Provider implementation or registry for constructing subsystem collaborators. + +### TabSizePropertyProvider + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/TabSizePropertyProvider.java` +- Declaration: `public class TabSizePropertyProvider implements PropertyProvider` +- Responsibility: Provider implementation or registry for constructing subsystem collaborators. + +### WhiteSpacePropertyProvider + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/WhiteSpacePropertyProvider.java` +- Declaration: `public class WhiteSpacePropertyProvider implements PropertyProvider` +- Responsibility: Provider implementation or registry for constructing subsystem collaborators. + +### ZIndexPropertyProvider + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/ZIndexPropertyProvider.java` +- Declaration: `public class ZIndexPropertyProvider implements PropertyProvider` +- Responsibility: Provider implementation or registry for constructing subsystem collaborators. diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/selector/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/selector/README.md new file mode 100644 index 00000000..9d81d0ab --- /dev/null +++ b/docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/selector/README.md @@ -0,0 +1,49 @@ +# com.spinyowl.spinygui.core.style.stylesheet.selector + +Selector contracts and base selector types. + +- Modules: core +- Source sets: main +- Direct classes: 4 +- Descendant packages: 4 + +## Classes + +### CombinatorSelector + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/CombinatorSelector.java` +- Declaration: `abstract class CombinatorSelector implements Selector` +- Responsibility: CSS selector implementation used to match elements and calculate specificity. + +### PseudoClassSelector + +- Kind: interface +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/PseudoClassSelector.java` +- Declaration: `public interface PseudoClassSelector extends Selector` +- Responsibility: Interface for pseudo-class selectors, which used to define a special state of an element. + +### PseudoElementSelector + +- Kind: interface +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/PseudoElementSelector.java` +- Declaration: `public interface PseudoElementSelector extends Selector` +- Responsibility: Interface for pseudo-element selectors, which used to define a special inner part of element. + +### Selector + +- Kind: interface +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/Selector.java` +- Declaration: `public interface Selector extends Comparable` +- Responsibility: Style selector interface. + +## Child Packages + +- [com.spinyowl.spinygui.core.style.stylesheet.selector.combinator](combinator/README.md) - Combinator selectors for descendant, child, sibling, adjacent sibling, and compound matching. +- [com.spinyowl.spinygui.core.style.stylesheet.selector.pseudoclass](pseudoclass/README.md) - Pseudo-class selector implementations. +- [com.spinyowl.spinygui.core.style.stylesheet.selector.pseudoelement](pseudoelement/README.md) - Pseudo-element selector implementations. +- [com.spinyowl.spinygui.core.style.stylesheet.selector.simple](simple/README.md) - Simple selectors for all, element, class, and id matching. + +## Aggregated Contents + +This package aggregates 4 descendant package(s) with 13 descendant class(es). diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/selector/combinator/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/selector/combinator/README.md new file mode 100644 index 00000000..58979afb --- /dev/null +++ b/docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/selector/combinator/README.md @@ -0,0 +1,45 @@ +# com.spinyowl.spinygui.core.style.stylesheet.selector.combinator + +Combinator selectors for descendant, child, sibling, adjacent sibling, and compound matching. + +- Modules: core +- Source sets: main +- Direct classes: 5 +- Descendant packages: 0 + +## Classes + +### AdjacentSiblingSelector + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/combinator/AdjacentSiblingSelector.java` +- Declaration: `public class AdjacentSiblingSelector extends CombinatorSelector` +- Responsibility: Adjacent Sibling Selector (+) The adjacent sibling selector is used to select an element that is directly after another specific element. + +### AndSelector + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/combinator/AndSelector.java` +- Declaration: `public class AndSelector extends CombinatorSelector` +- Responsibility: CSS selector implementation used to match elements and calculate specificity. + +### ChildSelector + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/combinator/ChildSelector.java` +- Declaration: `public class ChildSelector extends CombinatorSelector` +- Responsibility: Child Selector (>) The child selector selects all elements that are the children of a specified element. + +### DescendantSelector + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/combinator/DescendantSelector.java` +- Declaration: `public class DescendantSelector extends CombinatorSelector` +- Responsibility: Descendant Selector The descendant selector matches all elements that are descendants of a specified element. + +### GeneralSiblingSelector + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/combinator/GeneralSiblingSelector.java` +- Declaration: `public class GeneralSiblingSelector extends CombinatorSelector` +- Responsibility: General Sibling Selector (~) The general sibling selector selects all elements that are siblings of a specified element. diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/selector/pseudoclass/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/selector/pseudoclass/README.md new file mode 100644 index 00000000..8b825442 --- /dev/null +++ b/docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/selector/pseudoclass/README.md @@ -0,0 +1,17 @@ +# com.spinyowl.spinygui.core.style.stylesheet.selector.pseudoclass + +Pseudo-class selector implementations. + +- Modules: core +- Source sets: main +- Direct classes: 1 +- Descendant packages: 0 + +## Classes + +### HoverSelector + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/pseudoclass/HoverSelector.java` +- Declaration: `public class HoverSelector implements PseudoClassSelector` +- Responsibility: CSS selector implementation used to match elements and calculate specificity. diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/selector/pseudoelement/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/selector/pseudoelement/README.md new file mode 100644 index 00000000..961cad51 --- /dev/null +++ b/docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/selector/pseudoelement/README.md @@ -0,0 +1,31 @@ +# com.spinyowl.spinygui.core.style.stylesheet.selector.pseudoelement + +Pseudo-element selector implementations. + +- Modules: core +- Source sets: main +- Direct classes: 3 +- Descendant packages: 0 + +## Classes + +### AfterSelector + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/pseudoelement/AfterSelector.java` +- Declaration: `public class AfterSelector implements PseudoElementSelector` +- Responsibility: CSS selector implementation used to match elements and calculate specificity. + +### BeforeSelector + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/pseudoelement/BeforeSelector.java` +- Declaration: `public class BeforeSelector implements PseudoElementSelector` +- Responsibility: CSS selector implementation used to match elements and calculate specificity. + +### ScrollbarSelector + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/pseudoelement/ScrollbarSelector.java` +- Declaration: `public class ScrollbarSelector implements PseudoElementSelector` +- Responsibility: CSS selector implementation used to match elements and calculate specificity. diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/selector/simple/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/selector/simple/README.md new file mode 100644 index 00000000..aa26b5be --- /dev/null +++ b/docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/selector/simple/README.md @@ -0,0 +1,38 @@ +# com.spinyowl.spinygui.core.style.stylesheet.selector.simple + +Simple selectors for all, element, class, and id matching. + +- Modules: core +- Source sets: main +- Direct classes: 4 +- Descendant packages: 0 + +## Classes + +### AllSelector + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/simple/AllSelector.java` +- Declaration: `public class AllSelector implements Selector` +- Responsibility: CSS selector implementation used to match elements and calculate specificity. + +### ClassAttributeSelector + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/simple/ClassAttributeSelector.java` +- Declaration: `public class ClassAttributeSelector implements Selector` +- Responsibility: The class selector selects elements with a specific class attribute. + +### ElementSelector + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/simple/ElementSelector.java` +- Declaration: `public class ElementSelector implements Selector` +- Responsibility: The element selector selects elements based on the Element's nodeName. + +### IdAttributeSelector + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/simple/IdAttributeSelector.java` +- Declaration: `public class IdAttributeSelector implements Selector` +- Responsibility: The CSS ID selector matches an element based on the value of the element’s id attribute. diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/term/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/term/README.md new file mode 100644 index 00000000..4ba2bdf3 --- /dev/null +++ b/docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/term/README.md @@ -0,0 +1,73 @@ +# com.spinyowl.spinygui.core.style.stylesheet.term + +Typed CSS term values produced by parser visitors. + +- Modules: core +- Source sets: main +- Direct classes: 9 +- Descendant packages: 0 + +## Classes + +### TermColor + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/term/TermColor.java` +- Declaration: `public class TermColor extends Term` +- Responsibility: Represents term color in this package. + +### TermFloat + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/term/TermFloat.java` +- Declaration: `public class TermFloat extends Term` +- Responsibility: Represents term float in this package. + +### TermFunction + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/term/TermFunction.java` +- Declaration: `public class TermFunction extends TermList` +- Responsibility: Represents term function in this package. + +### TermIdent + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/term/TermIdent.java` +- Declaration: `public class TermIdent extends Term` +- Responsibility: Represents term ident in this package. + +### TermInteger + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/term/TermInteger.java` +- Declaration: `public class TermInteger extends Term` +- Responsibility: Represents term integer in this package. + +### TermLength + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/term/TermLength.java` +- Declaration: `public class TermLength extends TermUnit>` +- Responsibility: Represents term length in this package. + +### TermList + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/term/TermList.java` +- Declaration: `public class TermList extends Term>>` +- Responsibility: Represents term list in this package. + +### TermUnit + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/term/TermUnit.java` +- Declaration: `public class TermUnit extends Term` +- Responsibility: Represents term unit in this package. + +### TermURI + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/term/TermURI.java` +- Declaration: `public class TermURI extends Term` +- Responsibility: Represents term uri in this package. diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/util/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/util/README.md new file mode 100644 index 00000000..340d6ad6 --- /dev/null +++ b/docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/util/README.md @@ -0,0 +1,17 @@ +# com.spinyowl.spinygui.core.style.stylesheet.util + +Utility functions for converting and validating stylesheet values. + +- Modules: core +- Source sets: main +- Direct classes: 1 +- Descendant packages: 0 + +## Classes + +### StyleUtils + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/util/StyleUtils.java` +- Declaration: `final class StyleUtils` +- Responsibility: Static helper methods for the named subsystem. diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/core/style/types/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/core/style/types/README.md new file mode 100644 index 00000000..414d045e --- /dev/null +++ b/docs/project-structure/packages/com/spinyowl/spinygui/core/style/types/README.md @@ -0,0 +1,91 @@ +# com.spinyowl.spinygui.core.style.types + +Typed CSS value objects and constants for non-nested style domains. + +- Modules: core +- Source sets: main +- Direct classes: 10 +- Descendant packages: 4 + +## Classes + +### BorderRadius + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/types/BorderRadius.java` +- Declaration: `public class BorderRadius` +- Responsibility: Class container for border radius properties. + +### BoxShadow + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/types/BoxShadow.java` +- Declaration: `public class BoxShadow` +- Responsibility: Represents box shadow in this package. + +### Color + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/types/Color.java` +- Declaration: `public class Color` +- Responsibility: Represents color in this package. + +### Display + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/types/Display.java` +- Declaration: `final class Display` +- Responsibility: CSS display. + +### HorizontalAlign + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/types/HorizontalAlign.java` +- Declaration: `public class HorizontalAlign` +- Responsibility: Represents horizontal align in this package. + +### Overflow + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/types/Overflow.java` +- Declaration: `final class Overflow` +- Responsibility: Represents overflow in this package. + +### PointerEvents + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/types/PointerEvents.java` +- Declaration: `public class PointerEvents` +- Responsibility: Represents pointer events in this package. + +### Position + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/types/Position.java` +- Declaration: `final class Position` +- Responsibility: CSS position. + +### VerticalAlign + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/types/VerticalAlign.java` +- Declaration: `public class VerticalAlign` +- Responsibility: Represents vertical align in this package. + +### WhiteSpace + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/types/WhiteSpace.java` +- Declaration: `final class WhiteSpace` +- Responsibility: CSS white-space. + +## Child Packages + +- [com.spinyowl.spinygui.core.style.types.background](background/README.md) - Background-origin, repeat, and sizing value objects. +- [com.spinyowl.spinygui.core.style.types.border](border/README.md) - Border item and border-style value objects. +- [com.spinyowl.spinygui.core.style.types.flex](flex/README.md) - Flexbox alignment, direction, wrapping, and justification value constants. +- [com.spinyowl.spinygui.core.style.types.length](length/README.md) - CSS length units, length wrappers, and conversion contract. + +## Aggregated Contents + +This package aggregates 4 descendant package(s) with 14 descendant class(es). diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/core/style/types/background/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/core/style/types/background/README.md new file mode 100644 index 00000000..11f4fefe --- /dev/null +++ b/docs/project-structure/packages/com/spinyowl/spinygui/core/style/types/background/README.md @@ -0,0 +1,31 @@ +# com.spinyowl.spinygui.core.style.types.background + +Background-origin, repeat, and sizing value objects. + +- Modules: core +- Source sets: main +- Direct classes: 3 +- Descendant packages: 0 + +## Classes + +### BackgroundOrigin + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/types/background/BackgroundOrigin.java` +- Declaration: `public class BackgroundOrigin` +- Responsibility: Represents background origin in this package. + +### BackgroundRepeat + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/types/background/BackgroundRepeat.java` +- Declaration: `public class BackgroundRepeat` +- Responsibility: Represents background repeat in this package. + +### BackgroundSize + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/types/background/BackgroundSize.java` +- Declaration: `public class BackgroundSize` +- Responsibility: Represents background size in this package. diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/core/style/types/border/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/core/style/types/border/README.md new file mode 100644 index 00000000..02932a04 --- /dev/null +++ b/docs/project-structure/packages/com/spinyowl/spinygui/core/style/types/border/README.md @@ -0,0 +1,24 @@ +# com.spinyowl.spinygui.core.style.types.border + +Border item and border-style value objects. + +- Modules: core +- Source sets: main +- Direct classes: 2 +- Descendant packages: 0 + +## Classes + +### BorderItem + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/types/border/BorderItem.java` +- Declaration: `public class BorderItem` +- Responsibility: Represents border item in this package. + +### BorderStyle + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/types/border/BorderStyle.java` +- Declaration: `final class BorderStyle` +- Responsibility: CSS border style. diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/core/style/types/flex/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/core/style/types/flex/README.md new file mode 100644 index 00000000..4eb9d965 --- /dev/null +++ b/docs/project-structure/packages/com/spinyowl/spinygui/core/style/types/flex/README.md @@ -0,0 +1,52 @@ +# com.spinyowl.spinygui.core.style.types.flex + +Flexbox alignment, direction, wrapping, and justification value constants. + +- Modules: core +- Source sets: main +- Direct classes: 6 +- Descendant packages: 0 + +## Classes + +### AlignContent + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/types/flex/AlignContent.java` +- Declaration: `final class AlignContent` +- Responsibility: Specifies the alignment between the lines inside a flexible container when the items do not use all available space. + +### AlignItems + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/types/flex/AlignItems.java` +- Declaration: `final class AlignItems` +- Responsibility: Specifies the alignment for items inside a flexible container. + +### AlignSelf + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/types/flex/AlignSelf.java` +- Declaration: `final class AlignSelf` +- Responsibility: Specifies the alignment for selected items inside a flexible container. + +### FlexDirection + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/types/flex/FlexDirection.java` +- Declaration: `final class FlexDirection` +- Responsibility: Specifies the direction of the flexible items + +### FlexWrap + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/types/flex/FlexWrap.java` +- Declaration: `final class FlexWrap` +- Responsibility: Specifies whether the flexible items should wrap or not. + +### JustifyContent + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/types/flex/JustifyContent.java` +- Declaration: `final class JustifyContent` +- Responsibility: Specifies the alignment between the items inside a flexible container when the items do not use all available space. diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/core/style/types/length/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/core/style/types/length/README.md new file mode 100644 index 00000000..d4ebdf4f --- /dev/null +++ b/docs/project-structure/packages/com/spinyowl/spinygui/core/style/types/length/README.md @@ -0,0 +1,31 @@ +# com.spinyowl.spinygui.core.style.types.length + +CSS length units, length wrappers, and conversion contract. + +- Modules: core +- Source sets: main +- Direct classes: 3 +- Descendant packages: 0 + +## Classes + +### Length + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/types/length/Length.java` +- Declaration: `public class Length implements Unit` +- Responsibility: CSS unit/value object used by style conversion. + +### LengthConverter + +- Kind: interface +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/types/length/LengthConverter.java` +- Declaration: `public interface LengthConverter` +- Responsibility: Converts length to pixels. + +### Unit + +- Kind: interface +- Source: `core/src/main/java/com/spinyowl/spinygui/core/style/types/length/Unit.java` +- Declaration: `public interface Unit` +- Responsibility: Represents unit in this package. diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/core/system/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/core/system/README.md new file mode 100644 index 00000000..0bfb6352 --- /dev/null +++ b/docs/project-structure/packages/com/spinyowl/spinygui/core/system/README.md @@ -0,0 +1,22 @@ +# com.spinyowl.spinygui.core.system + +Package for system related classes. + +- Modules: core +- Source sets: main, test +- Direct classes: 0 +- Descendant packages: 7 + +## Classes + +No direct Java classes were found in this package. + +## Child Packages + +- [com.spinyowl.spinygui.core.system.event](event/README.md) - Raw platform/window/input events before conversion into application-level events. +- [com.spinyowl.spinygui.core.system.font](font/README.md) - Platform font loading, text metrics, and font storage abstractions. +- [com.spinyowl.spinygui.core.system.input](input/README.md) - Platform-facing key, modifier, action, and mouse-button enums. + +## Aggregated Contents + +This package aggregates 7 descendant package(s) with 60 descendant class(es). diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/core/system/event/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/core/system/event/README.md new file mode 100644 index 00000000..20c11405 --- /dev/null +++ b/docs/project-structure/packages/com/spinyowl/spinygui/core/system/event/README.md @@ -0,0 +1,132 @@ +# com.spinyowl.spinygui.core.system.event + +Raw platform/window/input events before conversion into application-level events. + +- Modules: core +- Source sets: main, test +- Direct classes: 16 +- Descendant packages: 3 + +## Classes + +### SystemCharEvent + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemCharEvent.java` +- Declaration: `public class SystemCharEvent extends SystemEvent` +- Responsibility: Unicode character is input. + +### SystemCharModsEvent + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemCharModsEvent.java` +- Declaration: `public class SystemCharModsEvent extends SystemEvent` +- Responsibility: Event on Unicode character input regardless of what modifier keys are used. + +### SystemCursorEnterEvent + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemCursorEnterEvent.java` +- Declaration: `public class SystemCursorEnterEvent extends SystemEvent` +- Responsibility: Event that generated when the cursor enters or leaves the client area of the window. + +### SystemCursorPosEvent + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemCursorPosEvent.java` +- Declaration: `public class SystemCursorPosEvent extends SystemEvent` +- Responsibility: Will be generated when the cursor is moved. + +### SystemEvent + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemEvent.java` +- Declaration: `public class SystemEvent` +- Responsibility: Defines tree of system events. + +### SystemFileDropEvent + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemFileDropEvent.java` +- Declaration: `public class SystemFileDropEvent extends SystemEvent` +- Responsibility: Event payload object for the named input/window/node change. + +### SystemFramebufferSizeEvent + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemFramebufferSizeEvent.java` +- Declaration: `public class SystemFramebufferSizeEvent extends SystemEvent` +- Responsibility: Will be generated when the framebuffer of the specified window is resized. + +### SystemKeyEvent + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemKeyEvent.java` +- Declaration: `public class SystemKeyEvent extends SystemEvent` +- Responsibility: Will be generated when a key is pressed, repeated or released. + +### SystemMouseClickEvent + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemMouseClickEvent.java` +- Declaration: `public class SystemMouseClickEvent extends SystemEvent` +- Responsibility: Will be generated when a mouse button is pressed or released. + +### SystemScrollEvent + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemScrollEvent.java` +- Declaration: `public class SystemScrollEvent extends SystemEvent` +- Responsibility: Will be generated when a scrolling device is used, such as a mouse wheel or scrolling area of a touchpad. + +### SystemWindowCloseEvent + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemWindowCloseEvent.java` +- Declaration: `public class SystemWindowCloseEvent extends SystemEvent` +- Responsibility: Will be generated when the user attempts to close the specified window, for example by clicking the close widget in the title bar. + +### SystemWindowFocusEvent + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemWindowFocusEvent.java` +- Declaration: `public class SystemWindowFocusEvent extends SystemEvent` +- Responsibility: Event payload object for the named input/window/node change. + +### SystemWindowIconifyEvent + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemWindowIconifyEvent.java` +- Declaration: `public class SystemWindowIconifyEvent extends SystemEvent` +- Responsibility: Will be generated when the specified window is iconified or restored. + +### SystemWindowPosEvent + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemWindowPosEvent.java` +- Declaration: `public class SystemWindowPosEvent extends SystemEvent` +- Responsibility: Will be generated when the specified window moves. + +### SystemWindowRefreshEvent + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemWindowRefreshEvent.java` +- Declaration: `public class SystemWindowRefreshEvent extends SystemEvent` +- Responsibility: Will be generated when the client area of the specified window needs to be redrawn, for example if the window has been exposed after having been covered by another window. + +### SystemWindowSizeEvent + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemWindowSizeEvent.java` +- Declaration: `public class SystemWindowSizeEvent extends SystemEvent` +- Responsibility: Will be generated when the specified window is resized. + +## Child Packages + +- [com.spinyowl.spinygui.core.system.event.listener](listener/README.md) - Adapters that translate raw system events into core event processing and state changes. +- [com.spinyowl.spinygui.core.system.event.processor](processor/README.md) - System-event processor contract and implementation for dispatching platform events. +- [com.spinyowl.spinygui.core.system.event.provider](provider/README.md) - Provider for mapping raw system event classes to listener instances. + +## Aggregated Contents + +This package aggregates 3 descendant package(s) with 31 descendant class(es). diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/core/system/event/listener/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/core/system/event/listener/README.md new file mode 100644 index 00000000..78a53ca8 --- /dev/null +++ b/docs/project-structure/packages/com/spinyowl/spinygui/core/system/event/listener/README.md @@ -0,0 +1,199 @@ +# com.spinyowl.spinygui.core.system.event.listener + +Adapters that translate raw system events into core event processing and state changes. + +- Modules: core +- Source sets: main, test +- Direct classes: 27 +- Descendant packages: 0 + +## Classes + +### AbstractSystemEventListener + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/system/event/listener/AbstractSystemEventListener.java` +- Declaration: `abstract class AbstractSystemEventListener implements SystemEventListener` +- Responsibility: System-event listener/adapter for translating platform events into core behavior. + +### SystemCharEventListener + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemCharEventListener.java` +- Declaration: `public class SystemCharEventListener extends AbstractSystemEventListener` +- Responsibility: System-event listener/adapter for translating platform events into core behavior. + +### SystemCharEventListenerTest + +- Kind: class +- Source: `core/src/test/java/com/spinyowl/spinygui/core/system/event/listener/SystemCharEventListenerTest.java` +- Declaration: `class SystemCharEventListenerTest` +- Responsibility: JUnit test fixture for the corresponding production component. + +### SystemCursorEnterEventListener + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemCursorEnterEventListener.java` +- Declaration: `public class SystemCursorEnterEventListener extends AbstractSystemEventListener` +- Responsibility: System-event listener/adapter for translating platform events into core behavior. + +### SystemCursorPosEventListener + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemCursorPosEventListener.java` +- Declaration: `public class SystemCursorPosEventListener extends AbstractSystemEventListener` +- Responsibility: System-event listener/adapter for translating platform events into core behavior. + +### SystemCursorPosEventListenerTest + +- Kind: class +- Source: `core/src/test/java/com/spinyowl/spinygui/core/system/event/listener/SystemCursorPosEventListenerTest.java` +- Declaration: `class SystemCursorPosEventListenerTest` +- Responsibility: JUnit test fixture for the corresponding production component. + +### SystemEventListener + +- Kind: interface +- Source: `core/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemEventListener.java` +- Declaration: `public interface SystemEventListener` +- Responsibility: Used to listen, process and translate system event to gui event. + +### SystemFileDropEventListener + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemFileDropEventListener.java` +- Declaration: `public class SystemFileDropEventListener extends AbstractSystemEventListener` +- Responsibility: System-event listener/adapter for translating platform events into core behavior. + +### SystemFileDropEventListenerTest + +- Kind: class +- Source: `core/src/test/java/com/spinyowl/spinygui/core/system/event/listener/SystemFileDropEventListenerTest.java` +- Declaration: `class SystemFileDropEventListenerTest` +- Responsibility: JUnit test fixture for the corresponding production component. + +### SystemKeyEventListener + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemKeyEventListener.java` +- Declaration: `public class SystemKeyEventListener extends AbstractSystemEventListener` +- Responsibility: System-event listener/adapter for translating platform events into core behavior. + +### SystemKeyEventListenerTest + +- Kind: class +- Source: `core/src/test/java/com/spinyowl/spinygui/core/system/event/listener/SystemKeyEventListenerTest.java` +- Declaration: `class SystemKeyEventListenerTest` +- Responsibility: JUnit test fixture for the corresponding production component. + +### SystemMouseClickEventListener + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemMouseClickEventListener.java` +- Declaration: `public class SystemMouseClickEventListener extends AbstractSystemEventListener` +- Responsibility: System-event listener/adapter for translating platform events into core behavior. + +### SystemMouseClickEventListenerTest + +- Kind: class +- Source: `core/src/test/java/com/spinyowl/spinygui/core/system/event/listener/SystemMouseClickEventListenerTest.java` +- Declaration: `class SystemMouseClickEventListenerTest` +- Responsibility: JUnit test fixture for the corresponding production component. + +### SystemScrollEventListener + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemScrollEventListener.java` +- Declaration: `public class SystemScrollEventListener extends AbstractSystemEventListener` +- Responsibility: System-event listener/adapter for translating platform events into core behavior. + +### SystemScrollEventListenerTest + +- Kind: class +- Source: `core/src/test/java/com/spinyowl/spinygui/core/system/event/listener/SystemScrollEventListenerTest.java` +- Declaration: `class SystemScrollEventListenerTest` +- Responsibility: JUnit test fixture for the corresponding production component. + +### SystemWindowFocusEventListener + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemWindowFocusEventListener.java` +- Declaration: `public class SystemWindowFocusEventListener extends AbstractSystemEventListener` +- Responsibility: System-event listener/adapter for translating platform events into core behavior. + +### SystemWindowFocusEventListenerTest + +- Kind: class +- Source: `core/src/test/java/com/spinyowl/spinygui/core/system/event/listener/SystemWindowFocusEventListenerTest.java` +- Declaration: `class SystemWindowFocusEventListenerTest` +- Responsibility: JUnit test fixture for the corresponding production component. + +### SystemWindowIconifyEventListener + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemWindowIconifyEventListener.java` +- Declaration: `public class SystemWindowIconifyEventListener extends AbstractSystemEventListener` +- Responsibility: System-event listener/adapter for translating platform events into core behavior. + +### SystemWindowIconifyEventListenerTest + +- Kind: class +- Source: `core/src/test/java/com/spinyowl/spinygui/core/system/event/listener/SystemWindowIconifyEventListenerTest.java` +- Declaration: `class SystemWindowIconifyEventListenerTest` +- Responsibility: JUnit test fixture for the corresponding production component. + +### SystemWindowPosEventListener + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemWindowPosEventListener.java` +- Declaration: `public class SystemWindowPosEventListener extends AbstractSystemEventListener` +- Responsibility: System-event listener/adapter for translating platform events into core behavior. + +### SystemWindowPosEventListenerTest + +- Kind: class +- Source: `core/src/test/java/com/spinyowl/spinygui/core/system/event/listener/SystemWindowPosEventListenerTest.java` +- Declaration: `class SystemWindowPosEventListenerTest` +- Responsibility: JUnit test fixture for the corresponding production component. + +### SystemWindowRefreshEventListener + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemWindowRefreshEventListener.java` +- Declaration: `public class SystemWindowRefreshEventListener extends AbstractSystemEventListener` +- Responsibility: System-event listener/adapter for translating platform events into core behavior. + +### SystemWindowRefreshEventListenerTest + +- Kind: class +- Source: `core/src/test/java/com/spinyowl/spinygui/core/system/event/listener/SystemWindowRefreshEventListenerTest.java` +- Declaration: `class SystemWindowRefreshEventListenerTest` +- Responsibility: JUnit test fixture for the corresponding production component. + +### SystemWindowsCloseEventListener + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemWindowsCloseEventListener.java` +- Declaration: `public class SystemWindowsCloseEventListener extends AbstractSystemEventListener` +- Responsibility: Generates WindowCloseEvent for frame and pushes it to EventProcessor. + +### SystemWindowsCloseEventListenerTest + +- Kind: class +- Source: `core/src/test/java/com/spinyowl/spinygui/core/system/event/listener/SystemWindowsCloseEventListenerTest.java` +- Declaration: `class SystemWindowsCloseEventListenerTest` +- Responsibility: JUnit test fixture for the corresponding production component. + +### SystemWindowSizeEventListener + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemWindowSizeEventListener.java` +- Declaration: `public class SystemWindowSizeEventListener extends AbstractSystemEventListener` +- Responsibility: System-event listener/adapter for translating platform events into core behavior. + +### SystemWindowSizeEventListenerTest + +- Kind: class +- Source: `core/src/test/java/com/spinyowl/spinygui/core/system/event/listener/SystemWindowSizeEventListenerTest.java` +- Declaration: `class SystemWindowSizeEventListenerTest` +- Responsibility: JUnit test fixture for the corresponding production component. diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/core/system/event/processor/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/core/system/event/processor/README.md new file mode 100644 index 00000000..08e27f5a --- /dev/null +++ b/docs/project-structure/packages/com/spinyowl/spinygui/core/system/event/processor/README.md @@ -0,0 +1,24 @@ +# com.spinyowl.spinygui.core.system.event.processor + +System-event processor contract and implementation for dispatching platform events. + +- Modules: core +- Source sets: main +- Direct classes: 2 +- Descendant packages: 0 + +## Classes + +### SystemEventProcessor + +- Kind: interface +- Source: `core/src/main/java/com/spinyowl/spinygui/core/system/event/processor/SystemEventProcessor.java` +- Declaration: `public interface SystemEventProcessor` +- Responsibility: System event processor. + +### SystemEventProcessorImpl + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/system/event/processor/SystemEventProcessorImpl.java` +- Declaration: `public class SystemEventProcessorImpl implements SystemEventProcessor` +- Responsibility: Default implementation based on two ConcurrentLinkedQueue queues which swapped every time during processing. diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/core/system/event/provider/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/core/system/event/provider/README.md new file mode 100644 index 00000000..cad06852 --- /dev/null +++ b/docs/project-structure/packages/com/spinyowl/spinygui/core/system/event/provider/README.md @@ -0,0 +1,24 @@ +# com.spinyowl.spinygui.core.system.event.provider + +Provider for mapping raw system event classes to listener instances. + +- Modules: core +- Source sets: main +- Direct classes: 2 +- Descendant packages: 0 + +## Classes + +### SystemEventListenerProvider + +- Kind: interface +- Source: `core/src/main/java/com/spinyowl/spinygui/core/system/event/provider/SystemEventListenerProvider.java` +- Declaration: `public interface SystemEventListenerProvider` +- Responsibility: Used to store system event class to system event listener mapping that would be used by SpinyGUI. + +### SystemEventListenerProviderImpl + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/system/event/provider/SystemEventListenerProviderImpl.java` +- Declaration: `public class SystemEventListenerProviderImpl implements SystemEventListenerProvider` +- Responsibility: Default implementation based on HashMap. diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/core/system/font/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/core/system/font/README.md new file mode 100644 index 00000000..f154665b --- /dev/null +++ b/docs/project-structure/packages/com/spinyowl/spinygui/core/system/font/README.md @@ -0,0 +1,67 @@ +# com.spinyowl.spinygui.core.system.font + +Platform font loading, text metrics, and font storage abstractions. + +- Modules: core +- Source sets: main +- Direct classes: 7 +- Descendant packages: 1 + +## Classes + +### FontDirectoriesProvider + +- Kind: interface +- Source: `core/src/main/java/com/spinyowl/spinygui/core/system/font/FontDirectoriesProvider.java` +- Declaration: `public interface FontDirectoriesProvider` +- Responsibility: Provider contract for supplying subsystem collaborators. + +### FontLoadingException + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/system/font/FontLoadingException.java` +- Declaration: `public class FontLoadingException extends RuntimeException` +- Responsibility: Represents font loading exception in this package. + +### FontService + +- Kind: interface +- Source: `core/src/main/java/com/spinyowl/spinygui/core/system/font/FontService.java` +- Declaration: `public interface FontService` +- Responsibility: Font service, responsible for loading and caching font data, and calculating text metrics. + +### FontStorage + +- Kind: interface +- Source: `core/src/main/java/com/spinyowl/spinygui/core/system/font/FontStorage.java` +- Declaration: `public interface FontStorage` +- Responsibility: Represents font storage in this package. + +### SystemFontLoader + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/system/font/SystemFontLoader.java` +- Declaration: `public class SystemFontLoader` +- Responsibility: Represents system font loader in this package. + +### TextLineMetrics + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/system/font/TextLineMetrics.java` +- Declaration: `final class TextLineMetrics` +- Responsibility: Represents text line metrics in this package. + +### TextMetrics + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/system/font/TextMetrics.java` +- Declaration: `final class TextMetrics` +- Responsibility: Represents text metrics in this package. + +## Child Packages + +- [com.spinyowl.spinygui.core.system.font.impl](impl/README.md) - Default font service, storage, and platform-specific font directory discovery. + +## Aggregated Contents + +This package aggregates 1 descendant package(s) with 3 descendant class(es). diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/core/system/font/impl/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/core/system/font/impl/README.md new file mode 100644 index 00000000..01399d06 --- /dev/null +++ b/docs/project-structure/packages/com/spinyowl/spinygui/core/system/font/impl/README.md @@ -0,0 +1,31 @@ +# com.spinyowl.spinygui.core.system.font.impl + +Default font service, storage, and platform-specific font directory discovery. + +- Modules: core +- Source sets: main +- Direct classes: 3 +- Descendant packages: 0 + +## Classes + +### FontServiceImpl + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/system/font/impl/FontServiceImpl.java` +- Declaration: `public class FontServiceImpl implements FontService` +- Responsibility: Default implementation of the matching interface. + +### FontStorageImpl + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/system/font/impl/FontStorageImpl.java` +- Declaration: `public class FontStorageImpl implements FontStorage` +- Responsibility: Default implementation of the matching interface. + +### PlatformSpecificFontDirectoriesProvider + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/system/font/impl/PlatformSpecificFontDirectoriesProvider.java` +- Declaration: `public class PlatformSpecificFontDirectoriesProvider implements FontDirectoriesProvider` +- Responsibility: Provider implementation or registry for constructing subsystem collaborators. diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/core/system/input/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/core/system/input/README.md new file mode 100644 index 00000000..cd4eaf5b --- /dev/null +++ b/docs/project-structure/packages/com/spinyowl/spinygui/core/system/input/README.md @@ -0,0 +1,31 @@ +# com.spinyowl.spinygui.core.system.input + +Platform-facing key, modifier, action, and mouse-button enums. + +- Modules: core +- Source sets: main +- Direct classes: 3 +- Descendant packages: 0 + +## Classes + +### SystemKeyAction + +- Kind: enum +- Source: `core/src/main/java/com/spinyowl/spinygui/core/system/input/SystemKeyAction.java` +- Declaration: `public enum SystemKeyAction` +- Responsibility: Enumerates supported values for the named domain concept. + +### SystemKeyMod + +- Kind: enum +- Source: `core/src/main/java/com/spinyowl/spinygui/core/system/input/SystemKeyMod.java` +- Declaration: `public enum SystemKeyMod` +- Responsibility: Enumerates supported values for the named domain concept. + +### SystemMouseButton + +- Kind: enum +- Source: `core/src/main/java/com/spinyowl/spinygui/core/system/input/SystemMouseButton.java` +- Declaration: `public enum SystemMouseButton` +- Responsibility: Enumerates supported values for the named domain concept. diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/core/time/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/core/time/README.md new file mode 100644 index 00000000..f406c84d --- /dev/null +++ b/docs/project-structure/packages/com/spinyowl/spinygui/core/time/README.md @@ -0,0 +1,17 @@ +# com.spinyowl.spinygui.core.time + +Time service abstraction for animation and frame timing. + +- Modules: core +- Source sets: main +- Direct classes: 1 +- Descendant packages: 0 + +## Classes + +### TimeService + +- Kind: interface +- Source: `core/src/main/java/com/spinyowl/spinygui/core/time/TimeService.java` +- Declaration: `public interface TimeService` +- Responsibility: Time provider interface. diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/core/util/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/core/util/README.md new file mode 100644 index 00000000..ecddbb55 --- /dev/null +++ b/docs/project-structure/packages/com/spinyowl/spinygui/core/util/README.md @@ -0,0 +1,59 @@ +# com.spinyowl.spinygui.core.util + +Small utilities for class-key maps, IO, node visibility, references, and text handling. + +- Modules: core +- Source sets: main, test +- Direct classes: 7 +- Descendant packages: 0 + +## Classes + +### ClassKeyMap + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/util/ClassKeyMap.java` +- Declaration: `public class ClassKeyMap` +- Responsibility: Used to store key-value structure, where key is class of type C and value is parameterized instance V, where generic arg is {@code }. + +### ClassKeyMapTest + +- Kind: class +- Source: `core/src/test/java/com/spinyowl/spinygui/core/util/ClassKeyMapTest.java` +- Declaration: `class ClassKeyMapTest` +- Responsibility: JUnit test fixture for the corresponding production component. + +### IOUtil + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/util/IOUtil.java` +- Declaration: `final class IOUtil` +- Responsibility: IO utility. + +### IOUtilTest + +- Kind: class +- Source: `core/src/test/java/com/spinyowl/spinygui/core/util/IOUtilTest.java` +- Declaration: `class IOUtilTest` +- Responsibility: JUnit test fixture for the corresponding production component. + +### NodeUtilities + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/util/NodeUtilities.java` +- Declaration: `final class NodeUtilities` +- Responsibility: Static helper methods for the named subsystem. + +### Reference + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/util/Reference.java` +- Declaration: `final class Reference` +- Responsibility: Represents reference in this package. + +### TextUtil + +- Kind: class +- Source: `core/src/main/java/com/spinyowl/spinygui/core/util/TextUtil.java` +- Declaration: `final class TextUtil` +- Responsibility: Static helper methods for the named subsystem. diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/demo/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/demo/README.md new file mode 100644 index 00000000..1b0b1cd3 --- /dev/null +++ b/docs/project-structure/packages/com/spinyowl/spinygui/demo/README.md @@ -0,0 +1,21 @@ +# com.spinyowl.spinygui.demo + +Demo namespace folder for runnable examples. + +- Modules: demo.complex, demo.simple +- Source sets: main +- Direct classes: 0 +- Descendant packages: 2 + +## Classes + +No direct Java classes were found in this package. + +## Child Packages + +- [com.spinyowl.spinygui.demo.complex](complex/README.md) - Windowed GLFW/LWJGL demo framework and concrete NanoVG demo. +- [com.spinyowl.spinygui.demo.simple](simple/README.md) - Simple demo entry points. + +## Aggregated Contents + +This package aggregates 2 descendant package(s) with 4 descendant class(es). diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/demo/complex/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/demo/complex/README.md new file mode 100644 index 00000000..fc2556b0 --- /dev/null +++ b/docs/project-structure/packages/com/spinyowl/spinygui/demo/complex/README.md @@ -0,0 +1,24 @@ +# com.spinyowl.spinygui.demo.complex + +Windowed GLFW/LWJGL demo framework and concrete NanoVG demo. + +- Modules: demo.complex +- Source sets: main +- Direct classes: 2 +- Descendant packages: 0 + +## Classes + +### Demo + +- Kind: class +- Source: `demo.complex/src/main/java/com/spinyowl/spinygui/demo/complex/Demo.java` +- Declaration: `abstract class Demo` +- Responsibility: Represents demo in this package. + +### NvgExample + +- Kind: class +- Source: `demo.complex/src/main/java/com/spinyowl/spinygui/demo/complex/NvgExample.java` +- Declaration: `public class NvgExample extends Demo` +- Responsibility: Represents nvg example in this package. diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/demo/simple/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/demo/simple/README.md new file mode 100644 index 00000000..3bd4eb84 --- /dev/null +++ b/docs/project-structure/packages/com/spinyowl/spinygui/demo/simple/README.md @@ -0,0 +1,24 @@ +# com.spinyowl.spinygui.demo.simple + +Simple demo entry points. + +- Modules: demo.simple +- Source sets: main +- Direct classes: 2 +- Descendant packages: 0 + +## Classes + +### Main + +- Kind: class +- Source: `demo.simple/src/main/java/com/spinyowl/spinygui/demo/simple/Main.java` +- Declaration: `public class Main` +- Responsibility: Represents main in this package. + +### OtherMain + +- Kind: class +- Source: `demo.simple/src/main/java/com/spinyowl/spinygui/demo/simple/OtherMain.java` +- Declaration: `public class OtherMain` +- Responsibility: Represents other main in this package. diff --git a/tools/generate_structure_docs.js b/tools/generate_structure_docs.js new file mode 100644 index 00000000..a36ddc6c --- /dev/null +++ b/tools/generate_structure_docs.js @@ -0,0 +1,395 @@ +const fs = require("fs"); +const path = require("path"); + +const root = process.cwd(); +const outRoot = path.join(root, "docs", "project-structure"); +const packagesRoot = path.join(outRoot, "packages"); + +const roots = [ + "core/src/main/java", + "core/src/test/java", + "core.backend/src/main/java", + "core.backend.lwjgl.nanovg/src/main/java", + "demo.simple/src/main/java", + "demo.complex/src/main/java", + "spinygui/src/main/java", +]; + +const moduleDescriptions = { + core: "Core DOM-like node model, CSS parser/style system, layout, events, input, fonts, animation, and platform abstraction.", + "core.backend": "Renderer backend API shared by concrete rendering implementations.", + "core.backend.lwjgl.nanovg": "LWJGL/NanoVG renderer implementation for drawing the core scene graph.", + "demo.simple": "Small launcher-style examples for exercising the aggregate SpinyGUI module.", + "demo.complex": "GLFW/LWJGL demo harness and NanoVG example runner.", + spinygui: "Aggregate module that re-exports the core and default backend modules.", +}; + +const packageDescriptions = new Map(Object.entries({ + "com": "Top-level Java namespace folder for project packages.", + "com.spinyowl": "SpinyOwl namespace folder.", + "com.spinyowl.spinygui": "SpinyGUI namespace folder aggregating core, backend, and demo packages.", + "com.spinyowl.spinygui.core": "Top-level core configuration and shared entry points for the GUI engine.", + "com.spinyowl.spinygui.core.backend": "Backend namespace folder for renderer APIs and implementations.", + "com.spinyowl.spinygui.core.animation": "Frame-time animation contracts and a simple animator loop.", + "com.spinyowl.spinygui.core.clipboard": "Clipboard abstraction used by platform integrations.", + "com.spinyowl.spinygui.core.cursor": "Cursor model and cursor service abstraction.", + "com.spinyowl.spinygui.core.event": "Application-level events emitted to nodes and event targets.", + "com.spinyowl.spinygui.core.event.listener": "Generic event listener contract for application events.", + "com.spinyowl.spinygui.core.event.processor": "Dispatch logic for routing application events to node listeners.", + "com.spinyowl.spinygui.core.font": "CSS-like font value objects: family, size, stretch, style, and weight.", + "com.spinyowl.spinygui.core.image": "Image abstraction used by style and rendering layers.", + "com.spinyowl.spinygui.core.input": "Input domain model for keyboard, mouse, shortcuts, and user-facing key mappings.", + "com.spinyowl.spinygui.core.input.impl": "Default mutable implementations of input services.", + "com.spinyowl.spinygui.core.layout": "Layout contracts, layout context, and text/element layout interfaces.", + "com.spinyowl.spinygui.core.layout.impl": "Concrete layout algorithms and utilities for block, flex, none, text, and layout tree updates.", + "com.spinyowl.spinygui.core.node": "DOM-like node hierarchy: frames, elements, empty elements, text nodes, and builders.", + "com.spinyowl.spinygui.core.node.intersection": "Hit-testing strategy objects for node intersection checks.", + "com.spinyowl.spinygui.core.node.layout": "Box-model geometry value objects used by layout and rendering.", + "com.spinyowl.spinygui.core.parser": "Parser interfaces for HTML-like node trees and stylesheets.", + "com.spinyowl.spinygui.core.parser.impl": "Default parser implementations and parser factory code.", + "com.spinyowl.spinygui.core.parser.impl.css": "CSS parser namespace containing generated ANTLR artifacts and handwritten semantic visitors.", + "com.spinyowl.spinygui.core.parser.impl.css.antlr": "Generated ANTLR CSS3 lexer/parser/listener/visitor artifacts. Regenerate from the grammar instead of hand-editing.", + "com.spinyowl.spinygui.core.parser.impl.css.visitor": "ANTLR visitors that convert CSS parse trees into stylesheet, selector, declaration, and term model objects.", + "com.spinyowl.spinygui.core.style": "Resolved style state applied to nodes after rule matching and property conversion.", + "com.spinyowl.spinygui.core.style.manager": "Style manager contract and implementation for applying stylesheets to node trees.", + "com.spinyowl.spinygui.core.style.stylesheet": "CSS stylesheet domain model: properties, rulesets, declarations, terms, specificity, and provider registry.", + "com.spinyowl.spinygui.core.style.stylesheet.annotation": "Annotations used by stylesheet property providers.", + "com.spinyowl.spinygui.core.style.stylesheet.atrule": "CSS at-rule model objects.", + "com.spinyowl.spinygui.core.style.stylesheet.impl": "Default property-store implementation and provider scanner integration.", + "com.spinyowl.spinygui.core.style.stylesheet.property": "CSS property providers that parse declarations into typed style values.", + "com.spinyowl.spinygui.core.style.stylesheet.selector": "Selector contracts and base selector types.", + "com.spinyowl.spinygui.core.style.stylesheet.selector.combinator": "Combinator selectors for descendant, child, sibling, adjacent sibling, and compound matching.", + "com.spinyowl.spinygui.core.style.stylesheet.selector.pseudoclass": "Pseudo-class selector implementations.", + "com.spinyowl.spinygui.core.style.stylesheet.selector.pseudoelement": "Pseudo-element selector implementations.", + "com.spinyowl.spinygui.core.style.stylesheet.selector.simple": "Simple selectors for all, element, class, and id matching.", + "com.spinyowl.spinygui.core.style.stylesheet.term": "Typed CSS term values produced by parser visitors.", + "com.spinyowl.spinygui.core.style.stylesheet.util": "Utility functions for converting and validating stylesheet values.", + "com.spinyowl.spinygui.core.style.types": "Typed CSS value objects and constants for non-nested style domains.", + "com.spinyowl.spinygui.core.style.types.background": "Background-origin, repeat, and sizing value objects.", + "com.spinyowl.spinygui.core.style.types.border": "Border item and border-style value objects.", + "com.spinyowl.spinygui.core.style.types.flex": "Flexbox alignment, direction, wrapping, and justification value constants.", + "com.spinyowl.spinygui.core.style.types.length": "CSS length units, length wrappers, and conversion contract.", + "com.spinyowl.spinygui.core.system.event": "Raw platform/window/input events before conversion into application-level events.", + "com.spinyowl.spinygui.core.system.event.listener": "Adapters that translate raw system events into core event processing and state changes.", + "com.spinyowl.spinygui.core.system.event.processor": "System-event processor contract and implementation for dispatching platform events.", + "com.spinyowl.spinygui.core.system.event.provider": "Provider for mapping raw system event classes to listener instances.", + "com.spinyowl.spinygui.core.system.font": "Platform font loading, text metrics, and font storage abstractions.", + "com.spinyowl.spinygui.core.system.font.impl": "Default font service, storage, and platform-specific font directory discovery.", + "com.spinyowl.spinygui.core.system.input": "Platform-facing key, modifier, action, and mouse-button enums.", + "com.spinyowl.spinygui.core.time": "Time service abstraction for animation and frame timing.", + "com.spinyowl.spinygui.core.util": "Small utilities for class-key maps, IO, node visibility, references, and text handling.", + "com.spinyowl.spinygui.core.backend.renderer": "Renderer SPI consumed by backend implementations.", + "com.spinyowl.spinygui.core.backend.renderer.lwjgl.nanovg": "NanoVG renderer orchestration and specialized element/text/border renderers.", + "com.spinyowl.spinygui.core.backend.renderer.lwjgl.nanovg.util": "NanoVG drawing and color helpers.", + "com.spinyowl.spinygui.demo.simple": "Simple demo entry points.", + "com.spinyowl.spinygui.demo.complex": "Windowed GLFW/LWJGL demo framework and concrete NanoVG demo.", + "com.spinyowl.spinygui.demo": "Demo namespace folder for runnable examples.", +})); + +function walk(dir) { + if (!fs.existsSync(dir)) return []; + const entries = fs.readdirSync(dir, { withFileTypes: true }); + return entries.flatMap((entry) => { + const p = path.join(dir, entry.name); + if (entry.isDirectory()) return walk(p); + return [p]; + }); +} + +function rel(p) { + return path.relative(root, p).replace(/\\/g, "/"); +} + +function moduleName(file) { + const r = rel(file); + const first = r.split("/")[0]; + return first; +} + +function sourceSet(file) { + const r = rel(file); + if (r.includes("/src/test/")) return "test"; + if (r.includes("/src/main/antlr/")) return "main/antlr"; + return "main"; +} + +function extractJavadocs(text, index) { + const before = text.slice(0, index); + const match = before.match(/\/\*\*([\s\S]*?)\*\/\s*(?:@\w[^\n]*\s*)*$/); + if (!match) return ""; + const cleaned = match[1] + .split(/\r?\n/) + .map((line) => line.replace(/^\s*\*\s?/, "").trim()) + .filter((line) => line && !line.startsWith("@")) + .join(" ") + .replace(/\{@link\s+([^}]+)\}/g, "$1") + .replace(/<[^>]+>/g, "") + .replace(/\s+/g, " ") + .trim(); + const sentence = cleaned.match(/^(.+?[.!?])(?:\s|$)/); + return sentence ? sentence[1] : cleaned; +} + +function declarationSummary(name, kind, declaration, file) { + const base = path.basename(file); + if (name.startsWith("CSS3") && file.includes(`${path.sep}antlr${path.sep}`)) { + return "Generated ANTLR CSS3 parser artifact; regenerate it from CSS3.g4 rather than editing by hand."; + } + if (name.endsWith("Test")) return "JUnit test fixture for the corresponding production component."; + if (kind === "interface" && name.endsWith("Service")) return "Service contract for the related subsystem."; + if (kind === "interface" && name.endsWith("Provider")) return "Provider contract for supplying subsystem collaborators."; + if (kind === "interface" && name.endsWith("Parser")) return "Parser contract for converting external text into core model objects."; + if (kind === "interface" && name.endsWith("Renderer")) return "Renderer contract for drawing a frame through a backend."; + if (name.endsWith("Impl")) return "Default implementation of the matching interface."; + if (name.endsWith("Provider")) return "Provider implementation or registry for constructing subsystem collaborators."; + if (name.endsWith("Visitor")) return "ANTLR visitor that maps parse-tree nodes into the stylesheet domain model."; + if (name.endsWith("Selector")) return "CSS selector implementation used to match elements and calculate specificity."; + if (name.endsWith("PropertyProvider")) return "CSS property parser that converts declaration terms into typed style properties."; + if (name.endsWith("EventListener")) return "System-event listener/adapter for translating platform events into core behavior."; + if (name.endsWith("Event")) return "Event payload object for the named input/window/node change."; + if (name.endsWith("Layout")) return "Layout algorithm or layout contract for the named display/text mode."; + if (name.endsWith("Util") || name.endsWith("Utils") || name.endsWith("Utilities")) return "Static helper methods for the named subsystem."; + if (kind === "enum") return "Enumerates supported values for the named domain concept."; + if (declaration.includes("extends Event")) return "Application-level event payload."; + if (declaration.includes("extends SystemEvent")) return "Raw platform event payload."; + if (declaration.includes("implements Unit")) return "CSS unit/value object used by style conversion."; + if (base === "module-info.java") return "Java module descriptor."; + return `Represents ${name.replace(/([a-z])([A-Z])/g, "$1 $2").toLowerCase()} in this package.`; +} + +function parseJava(file) { + const text = fs.readFileSync(file, "utf8"); + const pkg = (text.match(/^\s*package\s+([\w.]+)\s*;/m) || [])[1]; + if (!pkg) return null; + const classes = []; + const textWithoutComments = text + .replace(/\/\*[\s\S]*?\*\//g, (m) => " ".repeat(m.length)) + .replace(/\/\/[^\n\r]*/g, (m) => " ".repeat(m.length)); + const depthText = textWithoutComments + .replace(/"(?:\\.|[^"\\])*"/g, (m) => " ".repeat(m.length)) + .replace(/'(?:\\.|[^'\\])*'/g, (m) => " ".repeat(m.length)); + const topLevelDepthAt = (index) => { + let depth = 0; + for (let i = 0; i < index; i++) { + const c = depthText[i]; + if (c === "{") depth++; + else if (c === "}") depth--; + } + return depth; + }; + const re = /((?:public|protected|private|abstract|final|static|sealed|non-sealed)\s+)*(class|interface|enum|record|@interface)\s+(\w+)([^{;]*)/g; + let m; + while ((m = re.exec(textWithoutComments))) { + if (topLevelDepthAt(m.index) !== 0) continue; + const kind = m[2] === "@interface" ? "annotation" : m[2]; + const name = m[3]; + const declaration = `${(m[1] || "").trim()} ${m[2]} ${name}${m[4] || ""}`.replace(/\s+/g, " ").trim(); + const javadoc = extractJavadocs(text, m.index); + classes.push({ + name, + kind, + declaration, + source: rel(file), + summary: javadoc || declarationSummary(name, kind, declaration, file), + }); + } + return { pkg, module: moduleName(file), sourceSet: sourceSet(file), classes }; +} + +const javaFiles = roots.flatMap((r) => walk(path.join(root, r))).filter((f) => f.endsWith(".java")); +const packageMap = new Map(); +for (const file of javaFiles) { + const parsed = parseJava(file); + if (!parsed) continue; + if (!packageMap.has(parsed.pkg)) { + packageMap.set(parsed.pkg, { pkg: parsed.pkg, modules: new Set(), sourceSets: new Set(), classes: [] }); + } + const pkg = packageMap.get(parsed.pkg); + pkg.modules.add(parsed.module); + pkg.sourceSets.add(parsed.sourceSet); + pkg.classes.push(...parsed.classes); +} + +for (const pkg of [...packageMap.keys()]) { + const segments = pkg.split("."); + for (let i = 1; i < segments.length; i++) { + const prefix = segments.slice(0, i).join("."); + if (!packageMap.has(prefix)) { + packageMap.set(prefix, { pkg: prefix, modules: new Set(), sourceSets: new Set(), classes: [] }); + } + } +} + +for (const pkg of [...packageMap.keys()]) { + const info = packageMap.get(pkg); + const segments = pkg.split("."); + for (let i = 1; i < segments.length; i++) { + const prefixInfo = packageMap.get(segments.slice(0, i).join(".")); + for (const module of info.modules) prefixInfo.modules.add(module); + for (const sourceSet of info.sourceSets) prefixInfo.sourceSets.add(sourceSet); + } +} + +function packageDir(pkg) { + return path.join(packagesRoot, ...pkg.split(".")); +} + +function linkToPackage(pkg, fromFile) { + const target = path.join(packageDir(pkg), "README.md"); + return path.relative(path.dirname(fromFile), target).replace(/\\/g, "/"); +} + +function directChildren(pkg, allPackages) { + const prefix = `${pkg}.`; + return allPackages + .filter((p) => p.startsWith(prefix)) + .map((p) => p.slice(prefix.length).split(".")[0]) + .filter((v, i, a) => a.indexOf(v) === i) + .map((segment) => `${prefix}${segment}`) + .filter((child) => allPackages.includes(child)); +} + +function descendants(pkg, allPackages) { + const prefix = `${pkg}.`; + return allPackages.filter((p) => p.startsWith(prefix)); +} + +function packageDescription(pkg) { + if (packageDescriptions.has(pkg)) return packageDescriptions.get(pkg); + const last = pkg.split(".").pop(); + return `Package for ${last.replace(/([a-z])([A-Z])/g, "$1 $2").toLowerCase()} related classes.`; +} + +function writePackageDoc(pkg, allPackages) { + const info = packageMap.get(pkg); + const outFile = path.join(packageDir(pkg), "README.md"); + fs.mkdirSync(path.dirname(outFile), { recursive: true }); + const children = directChildren(pkg, allPackages); + const desc = descendants(pkg, allPackages); + const lines = []; + lines.push(`# ${pkg}`); + lines.push(""); + lines.push(packageDescription(pkg)); + lines.push(""); + lines.push(`- Modules: ${[...info.modules].sort().join(", ") || "none directly"}`); + lines.push(`- Source sets: ${[...info.sourceSets].sort().join(", ") || "none directly"}`); + lines.push(`- Direct classes: ${info.classes.length}`); + lines.push(`- Descendant packages: ${desc.length}`); + lines.push(""); + if (info.classes.length) { + lines.push("## Classes"); + lines.push(""); + for (const cls of info.classes.sort((a, b) => a.name.localeCompare(b.name))) { + lines.push(`### ${cls.name}`); + lines.push(""); + lines.push(`- Kind: ${cls.kind}`); + lines.push(`- Source: \`${cls.source}\``); + lines.push(`- Declaration: \`${cls.declaration}\``); + lines.push(`- Responsibility: ${cls.summary}`); + lines.push(""); + } + } else { + lines.push("## Classes"); + lines.push(""); + lines.push("No direct Java classes were found in this package."); + lines.push(""); + } + if (children.length) { + lines.push("## Child Packages"); + lines.push(""); + for (const child of children.sort()) { + lines.push(`- [${child}](${linkToPackage(child, outFile)}) - ${packageDescription(child)}`); + } + lines.push(""); + } + if (desc.length) { + lines.push("## Aggregated Contents"); + lines.push(""); + lines.push(`This package aggregates ${desc.length} descendant package(s) with ${desc.reduce((n, p) => n + packageMap.get(p).classes.length, 0)} descendant class(es).`); + lines.push(""); + } + fs.writeFileSync(outFile, lines.join("\n"), "utf8"); +} + +function packageDepth(pkg) { + return pkg.split(".").length; +} + +function rootPackageList(allPackages) { + return allPackages + .filter((p) => !allPackages.some((q) => p !== q && p.startsWith(`${q}.`) && packageDepth(q) === packageDepth(p) - 1)) + .sort(); +} + +function writeRootDoc(allPackages) { + const file = path.join(root, "PROJECT_STRUCTURE.md"); + const lines = []; + lines.push("# Project Structure"); + lines.push(""); + lines.push("SpinyGUI is a modular Java GUI library with a browser-engine-like split between node tree, CSS parsing/style resolution, layout, event processing, and rendering backends."); + lines.push(""); + lines.push("Generated package documents are stored under `docs/project-structure/packages/`. Each package document lists direct classes first, then links to child packages so the documentation can be read from deepest packages upward."); + lines.push(""); + lines.push("## Gradle Modules"); + lines.push(""); + for (const [name, description] of Object.entries(moduleDescriptions)) { + lines.push(`- \`${name}\` - ${description}`); + } + lines.push(""); + lines.push("## Main Subsystems"); + lines.push(""); + const subsystems = [ + ["Node tree", "core node classes model frames, elements, text, attributes, parent/child links, pseudo-state, and box geometry."], + ["Style and CSS", "stylesheet model, selectors, property providers, ANTLR visitors, typed CSS values, and `ResolvedStyle` convert parsed CSS into values usable by layout/rendering."], + ["Layout", "layout contracts and implementations calculate box-model rectangles, text metrics, normal-flow/positioned layout trees, scroll sizes, and client sizes."], + ["Events and input", "system events are translated by system listeners/processors into application events and node state changes."], + ["Fonts and metrics", "font service/storage abstractions load platform fonts and expose text metrics for layout/rendering."], + ["Rendering", "backend SPI defines `Renderer`; the LWJGL/NanoVG backend traverses layout nodes and draws elements, borders, and text."], + ["Demos", "simple and complex demo modules exercise the aggregate API and NanoVG backend."], + ]; + for (const [name, text] of subsystems) lines.push(`- ${name}: ${text}`); + lines.push(""); + lines.push("## Package Index"); + lines.push(""); + for (const pkg of allPackages) { + const info = packageMap.get(pkg); + lines.push(`- [${pkg}](${path.relative(root, path.join(packageDir(pkg), "README.md")).replace(/\\/g, "/")}) - ${info.classes.length} direct class(es); ${packageDescription(pkg)}`); + } + lines.push(""); + lines.push("## Reading Order"); + lines.push(""); + lines.push("For bottom-up navigation, start with the deepest packages such as `style.stylesheet.selector.*`, `style.stylesheet.term`, `style.types.*`, `system.event.listener`, `layout.impl`, and `backend.renderer.lwjgl.nanovg.util`; then move upward through their parent package documents and finish with this root overview."); + fs.writeFileSync(file, lines.join("\n"), "utf8"); +} + +function writeIndex(allPackages) { + fs.mkdirSync(outRoot, { recursive: true }); + const file = path.join(outRoot, "README.md"); + const lines = []; + lines.push("# Project Structure Documentation"); + lines.push(""); + lines.push("This folder mirrors Java package documentation generated from the project source tree."); + lines.push(""); + lines.push("- Root overview: [`PROJECT_STRUCTURE.md`](../../PROJECT_STRUCTURE.md)"); + lines.push("- Agent style/principles: [`AGENTS_CODE_STYLE.md`](../../AGENTS_CODE_STYLE.md)"); + lines.push(""); + lines.push("## Packages by Depth"); + lines.push(""); + for (const pkg of [...allPackages].sort((a, b) => packageDepth(b) - packageDepth(a) || a.localeCompare(b))) { + lines.push(`- [${pkg}](${path.relative(path.dirname(file), path.join(packageDir(pkg), "README.md")).replace(/\\/g, "/")})`); + } + fs.writeFileSync(file, lines.join("\n"), "utf8"); +} + +if (fs.existsSync(outRoot)) { + fs.rmSync(outRoot, { recursive: true, force: true }); +} + +const allPackages = [...packageMap.keys()].sort(); +for (const pkg of [...allPackages].sort((a, b) => packageDepth(b) - packageDepth(a) || a.localeCompare(b))) { + writePackageDoc(pkg, allPackages); +} +writeIndex(allPackages); +writeRootDoc(allPackages); + +console.log(`Wrote ${allPackages.length} package docs under ${rel(outRoot)}.`); From f61a8abd5daa913f6603a983ca04fbfe43854a79 Mon Sep 17 00:00:00 2001 From: SpinyOwl Date: Mon, 11 May 2026 23:53:05 +0200 Subject: [PATCH 002/121] Clarify project structure documentation references --- PROJECT_STRUCTURE.md | 122 ++++++++--------- docs/project-structure/README.md | 126 +++++++++--------- docs/project-structure/packages/com/README.md | 2 +- .../packages/com/spinyowl/README.md | 2 +- .../packages/com/spinyowl/spinygui/README.md | 4 +- .../com/spinyowl/spinygui/core/README.md | 30 ++--- .../spinyowl/spinygui/core/backend/README.md | 2 +- .../spinygui/core/backend/renderer/README.md | 2 +- .../core/backend/renderer/lwjgl/README.md | 2 +- .../backend/renderer/lwjgl/nanovg/README.md | 2 +- .../spinyowl/spinygui/core/event/README.md | 4 +- .../spinyowl/spinygui/core/input/README.md | 2 +- .../spinyowl/spinygui/core/layout/README.md | 2 +- .../com/spinyowl/spinygui/core/node/README.md | 4 +- .../spinyowl/spinygui/core/parser/README.md | 2 +- .../spinygui/core/parser/impl/README.md | 2 +- .../spinygui/core/parser/impl/css/README.md | 4 +- .../spinyowl/spinygui/core/style/README.md | 6 +- .../spinygui/core/style/stylesheet/README.md | 14 +- .../core/style/stylesheet/selector/README.md | 8 +- .../spinygui/core/style/types/README.md | 8 +- .../spinyowl/spinygui/core/system/README.md | 6 +- .../spinygui/core/system/event/README.md | 6 +- .../spinygui/core/system/font/README.md | 2 +- .../com/spinyowl/spinygui/demo/README.md | 4 +- tools/generate_structure_docs.js | 27 +++- 26 files changed, 205 insertions(+), 190 deletions(-) diff --git a/PROJECT_STRUCTURE.md b/PROJECT_STRUCTURE.md index cc497266..925ec4a3 100644 --- a/PROJECT_STRUCTURE.md +++ b/PROJECT_STRUCTURE.md @@ -25,67 +25,67 @@ Generated package documents are stored under `docs/project-structure/packages/`. ## Package Index -- [com](docs/project-structure/packages/com/README.md) - 0 direct class(es); Top-level Java namespace folder for project packages. -- [com.spinyowl](docs/project-structure/packages/com/spinyowl/README.md) - 0 direct class(es); SpinyOwl namespace folder. -- [com.spinyowl.spinygui](docs/project-structure/packages/com/spinyowl/spinygui/README.md) - 0 direct class(es); SpinyGUI namespace folder aggregating core, backend, and demo packages. -- [com.spinyowl.spinygui.core](docs/project-structure/packages/com/spinyowl/spinygui/core/README.md) - 1 direct class(es); Top-level core configuration and shared entry points for the GUI engine. -- [com.spinyowl.spinygui.core.animation](docs/project-structure/packages/com/spinyowl/spinygui/core/animation/README.md) - 3 direct class(es); Frame-time animation contracts and a simple animator loop. -- [com.spinyowl.spinygui.core.backend](docs/project-structure/packages/com/spinyowl/spinygui/core/backend/README.md) - 0 direct class(es); Backend namespace folder for renderer APIs and implementations. -- [com.spinyowl.spinygui.core.backend.renderer](docs/project-structure/packages/com/spinyowl/spinygui/core/backend/renderer/README.md) - 1 direct class(es); Renderer SPI consumed by backend implementations. -- [com.spinyowl.spinygui.core.backend.renderer.lwjgl](docs/project-structure/packages/com/spinyowl/spinygui/core/backend/renderer/lwjgl/README.md) - 0 direct class(es); Package for lwjgl related classes. -- [com.spinyowl.spinygui.core.backend.renderer.lwjgl.nanovg](docs/project-structure/packages/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/README.md) - 4 direct class(es); NanoVG renderer orchestration and specialized element/text/border renderers. -- [com.spinyowl.spinygui.core.backend.renderer.lwjgl.nanovg.util](docs/project-structure/packages/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/util/README.md) - 3 direct class(es); NanoVG drawing and color helpers. -- [com.spinyowl.spinygui.core.clipboard](docs/project-structure/packages/com/spinyowl/spinygui/core/clipboard/README.md) - 1 direct class(es); Clipboard abstraction used by platform integrations. -- [com.spinyowl.spinygui.core.cursor](docs/project-structure/packages/com/spinyowl/spinygui/core/cursor/README.md) - 3 direct class(es); Cursor model and cursor service abstraction. -- [com.spinyowl.spinygui.core.event](docs/project-structure/packages/com/spinyowl/spinygui/core/event/README.md) - 21 direct class(es); Application-level events emitted to nodes and event targets. -- [com.spinyowl.spinygui.core.event.listener](docs/project-structure/packages/com/spinyowl/spinygui/core/event/listener/README.md) - 1 direct class(es); Generic event listener contract for application events. -- [com.spinyowl.spinygui.core.event.processor](docs/project-structure/packages/com/spinyowl/spinygui/core/event/processor/README.md) - 2 direct class(es); Dispatch logic for routing application events to node listeners. -- [com.spinyowl.spinygui.core.font](docs/project-structure/packages/com/spinyowl/spinygui/core/font/README.md) - 5 direct class(es); CSS-like font value objects: family, size, stretch, style, and weight. -- [com.spinyowl.spinygui.core.image](docs/project-structure/packages/com/spinyowl/spinygui/core/image/README.md) - 1 direct class(es); Image abstraction used by style and rendering layers. -- [com.spinyowl.spinygui.core.input](docs/project-structure/packages/com/spinyowl/spinygui/core/input/README.md) - 10 direct class(es); Input domain model for keyboard, mouse, shortcuts, and user-facing key mappings. -- [com.spinyowl.spinygui.core.input.impl](docs/project-structure/packages/com/spinyowl/spinygui/core/input/impl/README.md) - 3 direct class(es); Default mutable implementations of input services. -- [com.spinyowl.spinygui.core.layout](docs/project-structure/packages/com/spinyowl/spinygui/core/layout/README.md) - 5 direct class(es); Layout contracts, layout context, and text/element layout interfaces. -- [com.spinyowl.spinygui.core.layout.impl](docs/project-structure/packages/com/spinyowl/spinygui/core/layout/impl/README.md) - 7 direct class(es); Concrete layout algorithms and utilities for block, flex, none, text, and layout tree updates. -- [com.spinyowl.spinygui.core.node](docs/project-structure/packages/com/spinyowl/spinygui/core/node/README.md) - 6 direct class(es); DOM-like node hierarchy: frames, elements, empty elements, text nodes, and builders. -- [com.spinyowl.spinygui.core.node.intersection](docs/project-structure/packages/com/spinyowl/spinygui/core/node/intersection/README.md) - 3 direct class(es); Hit-testing strategy objects for node intersection checks. -- [com.spinyowl.spinygui.core.node.layout](docs/project-structure/packages/com/spinyowl/spinygui/core/node/layout/README.md) - 3 direct class(es); Box-model geometry value objects used by layout and rendering. -- [com.spinyowl.spinygui.core.parser](docs/project-structure/packages/com/spinyowl/spinygui/core/parser/README.md) - 2 direct class(es); Parser interfaces for HTML-like node trees and stylesheets. -- [com.spinyowl.spinygui.core.parser.impl](docs/project-structure/packages/com/spinyowl/spinygui/core/parser/impl/README.md) - 4 direct class(es); Default parser implementations and parser factory code. -- [com.spinyowl.spinygui.core.parser.impl.css](docs/project-structure/packages/com/spinyowl/spinygui/core/parser/impl/css/README.md) - 0 direct class(es); CSS parser namespace containing generated ANTLR artifacts and handwritten semantic visitors. -- [com.spinyowl.spinygui.core.parser.impl.css.antlr](docs/project-structure/packages/com/spinyowl/spinygui/core/parser/impl/css/antlr/README.md) - 6 direct class(es); Generated ANTLR CSS3 lexer/parser/listener/visitor artifacts. Regenerate from the grammar instead of hand-editing. -- [com.spinyowl.spinygui.core.parser.impl.css.visitor](docs/project-structure/packages/com/spinyowl/spinygui/core/parser/impl/css/visitor/README.md) - 8 direct class(es); ANTLR visitors that convert CSS parse trees into stylesheet, selector, declaration, and term model objects. -- [com.spinyowl.spinygui.core.style](docs/project-structure/packages/com/spinyowl/spinygui/core/style/README.md) - 1 direct class(es); Resolved style state applied to nodes after rule matching and property conversion. -- [com.spinyowl.spinygui.core.style.manager](docs/project-structure/packages/com/spinyowl/spinygui/core/style/manager/README.md) - 2 direct class(es); Style manager contract and implementation for applying stylesheets to node trees. -- [com.spinyowl.spinygui.core.style.stylesheet](docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/README.md) - 12 direct class(es); CSS stylesheet domain model: properties, rulesets, declarations, terms, specificity, and provider registry. -- [com.spinyowl.spinygui.core.style.stylesheet.annotation](docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/annotation/README.md) - 1 direct class(es); Annotations used by stylesheet property providers. -- [com.spinyowl.spinygui.core.style.stylesheet.atrule](docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/atrule/README.md) - 1 direct class(es); CSS at-rule model objects. -- [com.spinyowl.spinygui.core.style.stylesheet.impl](docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/impl/README.md) - 2 direct class(es); Default property-store implementation and provider scanner integration. -- [com.spinyowl.spinygui.core.style.stylesheet.property](docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/property/README.md) - 21 direct class(es); CSS property providers that parse declarations into typed style values. -- [com.spinyowl.spinygui.core.style.stylesheet.selector](docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/selector/README.md) - 4 direct class(es); Selector contracts and base selector types. -- [com.spinyowl.spinygui.core.style.stylesheet.selector.combinator](docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/selector/combinator/README.md) - 5 direct class(es); Combinator selectors for descendant, child, sibling, adjacent sibling, and compound matching. -- [com.spinyowl.spinygui.core.style.stylesheet.selector.pseudoclass](docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/selector/pseudoclass/README.md) - 1 direct class(es); Pseudo-class selector implementations. -- [com.spinyowl.spinygui.core.style.stylesheet.selector.pseudoelement](docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/selector/pseudoelement/README.md) - 3 direct class(es); Pseudo-element selector implementations. -- [com.spinyowl.spinygui.core.style.stylesheet.selector.simple](docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/selector/simple/README.md) - 4 direct class(es); Simple selectors for all, element, class, and id matching. -- [com.spinyowl.spinygui.core.style.stylesheet.term](docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/term/README.md) - 9 direct class(es); Typed CSS term values produced by parser visitors. -- [com.spinyowl.spinygui.core.style.stylesheet.util](docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/util/README.md) - 1 direct class(es); Utility functions for converting and validating stylesheet values. -- [com.spinyowl.spinygui.core.style.types](docs/project-structure/packages/com/spinyowl/spinygui/core/style/types/README.md) - 10 direct class(es); Typed CSS value objects and constants for non-nested style domains. -- [com.spinyowl.spinygui.core.style.types.background](docs/project-structure/packages/com/spinyowl/spinygui/core/style/types/background/README.md) - 3 direct class(es); Background-origin, repeat, and sizing value objects. -- [com.spinyowl.spinygui.core.style.types.border](docs/project-structure/packages/com/spinyowl/spinygui/core/style/types/border/README.md) - 2 direct class(es); Border item and border-style value objects. -- [com.spinyowl.spinygui.core.style.types.flex](docs/project-structure/packages/com/spinyowl/spinygui/core/style/types/flex/README.md) - 6 direct class(es); Flexbox alignment, direction, wrapping, and justification value constants. -- [com.spinyowl.spinygui.core.style.types.length](docs/project-structure/packages/com/spinyowl/spinygui/core/style/types/length/README.md) - 3 direct class(es); CSS length units, length wrappers, and conversion contract. -- [com.spinyowl.spinygui.core.system](docs/project-structure/packages/com/spinyowl/spinygui/core/system/README.md) - 0 direct class(es); Package for system related classes. -- [com.spinyowl.spinygui.core.system.event](docs/project-structure/packages/com/spinyowl/spinygui/core/system/event/README.md) - 16 direct class(es); Raw platform/window/input events before conversion into application-level events. -- [com.spinyowl.spinygui.core.system.event.listener](docs/project-structure/packages/com/spinyowl/spinygui/core/system/event/listener/README.md) - 27 direct class(es); Adapters that translate raw system events into core event processing and state changes. -- [com.spinyowl.spinygui.core.system.event.processor](docs/project-structure/packages/com/spinyowl/spinygui/core/system/event/processor/README.md) - 2 direct class(es); System-event processor contract and implementation for dispatching platform events. -- [com.spinyowl.spinygui.core.system.event.provider](docs/project-structure/packages/com/spinyowl/spinygui/core/system/event/provider/README.md) - 2 direct class(es); Provider for mapping raw system event classes to listener instances. -- [com.spinyowl.spinygui.core.system.font](docs/project-structure/packages/com/spinyowl/spinygui/core/system/font/README.md) - 7 direct class(es); Platform font loading, text metrics, and font storage abstractions. -- [com.spinyowl.spinygui.core.system.font.impl](docs/project-structure/packages/com/spinyowl/spinygui/core/system/font/impl/README.md) - 3 direct class(es); Default font service, storage, and platform-specific font directory discovery. -- [com.spinyowl.spinygui.core.system.input](docs/project-structure/packages/com/spinyowl/spinygui/core/system/input/README.md) - 3 direct class(es); Platform-facing key, modifier, action, and mouse-button enums. -- [com.spinyowl.spinygui.core.time](docs/project-structure/packages/com/spinyowl/spinygui/core/time/README.md) - 1 direct class(es); Time service abstraction for animation and frame timing. -- [com.spinyowl.spinygui.core.util](docs/project-structure/packages/com/spinyowl/spinygui/core/util/README.md) - 7 direct class(es); Small utilities for class-key maps, IO, node visibility, references, and text handling. -- [com.spinyowl.spinygui.demo](docs/project-structure/packages/com/spinyowl/spinygui/demo/README.md) - 0 direct class(es); Demo namespace folder for runnable examples. -- [com.spinyowl.spinygui.demo.complex](docs/project-structure/packages/com/spinyowl/spinygui/demo/complex/README.md) - 2 direct class(es); Windowed GLFW/LWJGL demo framework and concrete NanoVG demo. -- [com.spinyowl.spinygui.demo.simple](docs/project-structure/packages/com/spinyowl/spinygui/demo/simple/README.md) - 2 direct class(es); Simple demo entry points. +- [com](docs/project-structure/packages/com/README.md) - This reference describes Top-level Java namespace folder for project packages, lists 0 direct classes, and aggregates 60 descendant packages. +- [com.spinyowl](docs/project-structure/packages/com/spinyowl/README.md) - This reference describes SpinyOwl namespace folder, lists 0 direct classes, and aggregates 59 descendant packages. +- [com.spinyowl.spinygui](docs/project-structure/packages/com/spinyowl/spinygui/README.md) - This reference describes SpinyGUI namespace folder aggregating core, backend, and demo packages, lists 0 direct classes, and aggregates 58 descendant packages. +- [com.spinyowl.spinygui.core](docs/project-structure/packages/com/spinyowl/spinygui/core/README.md) - This reference describes Top-level core configuration and shared entry points for the GUI engine, lists 1 direct class, and aggregates 54 descendant packages. +- [com.spinyowl.spinygui.core.animation](docs/project-structure/packages/com/spinyowl/spinygui/core/animation/README.md) - This reference describes Frame-time animation contracts and a simple animator loop, lists 3 direct classes, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.backend](docs/project-structure/packages/com/spinyowl/spinygui/core/backend/README.md) - This reference describes Backend namespace folder for renderer APIs and implementations, lists 0 direct classes, and aggregates 4 descendant packages. +- [com.spinyowl.spinygui.core.backend.renderer](docs/project-structure/packages/com/spinyowl/spinygui/core/backend/renderer/README.md) - This reference describes Renderer SPI consumed by backend implementations, lists 1 direct class, and aggregates 3 descendant packages. +- [com.spinyowl.spinygui.core.backend.renderer.lwjgl](docs/project-structure/packages/com/spinyowl/spinygui/core/backend/renderer/lwjgl/README.md) - This reference describes Package for lwjgl related classes, lists 0 direct classes, and aggregates 2 descendant packages. +- [com.spinyowl.spinygui.core.backend.renderer.lwjgl.nanovg](docs/project-structure/packages/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/README.md) - This reference describes NanoVG renderer orchestration and specialized element/text/border renderers, lists 4 direct classes, and aggregates 1 descendant package. +- [com.spinyowl.spinygui.core.backend.renderer.lwjgl.nanovg.util](docs/project-structure/packages/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/util/README.md) - This reference describes NanoVG drawing and color helpers, lists 3 direct classes, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.clipboard](docs/project-structure/packages/com/spinyowl/spinygui/core/clipboard/README.md) - This reference describes Clipboard abstraction used by platform integrations, lists 1 direct class, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.cursor](docs/project-structure/packages/com/spinyowl/spinygui/core/cursor/README.md) - This reference describes Cursor model and cursor service abstraction, lists 3 direct classes, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.event](docs/project-structure/packages/com/spinyowl/spinygui/core/event/README.md) - This reference describes Application-level events emitted to nodes and event targets, lists 21 direct classes, and aggregates 2 descendant packages. +- [com.spinyowl.spinygui.core.event.listener](docs/project-structure/packages/com/spinyowl/spinygui/core/event/listener/README.md) - This reference describes Generic event listener contract for application events, lists 1 direct class, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.event.processor](docs/project-structure/packages/com/spinyowl/spinygui/core/event/processor/README.md) - This reference describes Dispatch logic for routing application events to node listeners, lists 2 direct classes, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.font](docs/project-structure/packages/com/spinyowl/spinygui/core/font/README.md) - This reference describes CSS-like font value objects: family, size, stretch, style, and weight, lists 5 direct classes, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.image](docs/project-structure/packages/com/spinyowl/spinygui/core/image/README.md) - This reference describes Image abstraction used by style and rendering layers, lists 1 direct class, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.input](docs/project-structure/packages/com/spinyowl/spinygui/core/input/README.md) - This reference describes Input domain model for keyboard, mouse, shortcuts, and user-facing key mappings, lists 10 direct classes, and aggregates 1 descendant package. +- [com.spinyowl.spinygui.core.input.impl](docs/project-structure/packages/com/spinyowl/spinygui/core/input/impl/README.md) - This reference describes Default mutable implementations of input services, lists 3 direct classes, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.layout](docs/project-structure/packages/com/spinyowl/spinygui/core/layout/README.md) - This reference describes Layout contracts, layout context, and text/element layout interfaces, lists 5 direct classes, and aggregates 1 descendant package. +- [com.spinyowl.spinygui.core.layout.impl](docs/project-structure/packages/com/spinyowl/spinygui/core/layout/impl/README.md) - This reference describes Concrete layout algorithms and utilities for block, flex, none, text, and layout tree updates, lists 7 direct classes, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.node](docs/project-structure/packages/com/spinyowl/spinygui/core/node/README.md) - This reference describes DOM-like node hierarchy: frames, elements, empty elements, text nodes, and builders, lists 6 direct classes, and aggregates 2 descendant packages. +- [com.spinyowl.spinygui.core.node.intersection](docs/project-structure/packages/com/spinyowl/spinygui/core/node/intersection/README.md) - This reference describes Hit-testing strategy objects for node intersection checks, lists 3 direct classes, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.node.layout](docs/project-structure/packages/com/spinyowl/spinygui/core/node/layout/README.md) - This reference describes Box-model geometry value objects used by layout and rendering, lists 3 direct classes, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.parser](docs/project-structure/packages/com/spinyowl/spinygui/core/parser/README.md) - This reference describes Parser interfaces for HTML-like node trees and stylesheets, lists 2 direct classes, and aggregates 4 descendant packages. +- [com.spinyowl.spinygui.core.parser.impl](docs/project-structure/packages/com/spinyowl/spinygui/core/parser/impl/README.md) - This reference describes Default parser implementations and parser factory code, lists 4 direct classes, and aggregates 3 descendant packages. +- [com.spinyowl.spinygui.core.parser.impl.css](docs/project-structure/packages/com/spinyowl/spinygui/core/parser/impl/css/README.md) - This reference describes CSS parser namespace containing generated ANTLR artifacts and handwritten semantic visitors, lists 0 direct classes, and aggregates 2 descendant packages. +- [com.spinyowl.spinygui.core.parser.impl.css.antlr](docs/project-structure/packages/com/spinyowl/spinygui/core/parser/impl/css/antlr/README.md) - This reference describes Generated ANTLR CSS3 lexer/parser/listener/visitor artifacts; Regenerate from the grammar instead of hand-editing, lists 6 direct classes, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.parser.impl.css.visitor](docs/project-structure/packages/com/spinyowl/spinygui/core/parser/impl/css/visitor/README.md) - This reference describes ANTLR visitors that convert CSS parse trees into stylesheet, selector, declaration, and term model objects, lists 8 direct classes, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.style](docs/project-structure/packages/com/spinyowl/spinygui/core/style/README.md) - This reference describes Resolved style state applied to nodes after rule matching and property conversion, lists 1 direct class, and aggregates 18 descendant packages. +- [com.spinyowl.spinygui.core.style.manager](docs/project-structure/packages/com/spinyowl/spinygui/core/style/manager/README.md) - This reference describes Style manager contract and implementation for applying stylesheets to node trees, lists 2 direct classes, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.style.stylesheet](docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/README.md) - This reference describes CSS stylesheet domain model: properties, rulesets, declarations, terms, specificity, and provider registry, lists 12 direct classes, and aggregates 11 descendant packages. +- [com.spinyowl.spinygui.core.style.stylesheet.annotation](docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/annotation/README.md) - This reference describes Annotations used by stylesheet property providers, lists 1 direct class, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.style.stylesheet.atrule](docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/atrule/README.md) - This reference describes CSS at-rule model objects, lists 1 direct class, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.style.stylesheet.impl](docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/impl/README.md) - This reference describes Default property-store implementation and provider scanner integration, lists 2 direct classes, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.style.stylesheet.property](docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/property/README.md) - This reference describes CSS property providers that parse declarations into typed style values, lists 21 direct classes, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.style.stylesheet.selector](docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/selector/README.md) - This reference describes Selector contracts and base selector types, lists 4 direct classes, and aggregates 4 descendant packages. +- [com.spinyowl.spinygui.core.style.stylesheet.selector.combinator](docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/selector/combinator/README.md) - This reference describes Combinator selectors for descendant, child, sibling, adjacent sibling, and compound matching, lists 5 direct classes, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.style.stylesheet.selector.pseudoclass](docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/selector/pseudoclass/README.md) - This reference describes Pseudo-class selector implementations, lists 1 direct class, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.style.stylesheet.selector.pseudoelement](docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/selector/pseudoelement/README.md) - This reference describes Pseudo-element selector implementations, lists 3 direct classes, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.style.stylesheet.selector.simple](docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/selector/simple/README.md) - This reference describes Simple selectors for all, element, class, and id matching, lists 4 direct classes, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.style.stylesheet.term](docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/term/README.md) - This reference describes Typed CSS term values produced by parser visitors, lists 9 direct classes, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.style.stylesheet.util](docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/util/README.md) - This reference describes Utility functions for converting and validating stylesheet values, lists 1 direct class, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.style.types](docs/project-structure/packages/com/spinyowl/spinygui/core/style/types/README.md) - This reference describes Typed CSS value objects and constants for non-nested style domains, lists 10 direct classes, and aggregates 4 descendant packages. +- [com.spinyowl.spinygui.core.style.types.background](docs/project-structure/packages/com/spinyowl/spinygui/core/style/types/background/README.md) - This reference describes Background-origin, repeat, and sizing value objects, lists 3 direct classes, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.style.types.border](docs/project-structure/packages/com/spinyowl/spinygui/core/style/types/border/README.md) - This reference describes Border item and border-style value objects, lists 2 direct classes, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.style.types.flex](docs/project-structure/packages/com/spinyowl/spinygui/core/style/types/flex/README.md) - This reference describes Flexbox alignment, direction, wrapping, and justification value constants, lists 6 direct classes, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.style.types.length](docs/project-structure/packages/com/spinyowl/spinygui/core/style/types/length/README.md) - This reference describes CSS length units, length wrappers, and conversion contract, lists 3 direct classes, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.system](docs/project-structure/packages/com/spinyowl/spinygui/core/system/README.md) - This reference describes Package for system related classes, lists 0 direct classes, and aggregates 7 descendant packages. +- [com.spinyowl.spinygui.core.system.event](docs/project-structure/packages/com/spinyowl/spinygui/core/system/event/README.md) - This reference describes Raw platform/window/input events before conversion into application-level events, lists 16 direct classes, and aggregates 3 descendant packages. +- [com.spinyowl.spinygui.core.system.event.listener](docs/project-structure/packages/com/spinyowl/spinygui/core/system/event/listener/README.md) - This reference describes Adapters that translate raw system events into core event processing and state changes, lists 27 direct classes, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.system.event.processor](docs/project-structure/packages/com/spinyowl/spinygui/core/system/event/processor/README.md) - This reference describes System-event processor contract and implementation for dispatching platform events, lists 2 direct classes, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.system.event.provider](docs/project-structure/packages/com/spinyowl/spinygui/core/system/event/provider/README.md) - This reference describes Provider for mapping raw system event classes to listener instances, lists 2 direct classes, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.system.font](docs/project-structure/packages/com/spinyowl/spinygui/core/system/font/README.md) - This reference describes Platform font loading, text metrics, and font storage abstractions, lists 7 direct classes, and aggregates 1 descendant package. +- [com.spinyowl.spinygui.core.system.font.impl](docs/project-structure/packages/com/spinyowl/spinygui/core/system/font/impl/README.md) - This reference describes Default font service, storage, and platform-specific font directory discovery, lists 3 direct classes, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.system.input](docs/project-structure/packages/com/spinyowl/spinygui/core/system/input/README.md) - This reference describes Platform-facing key, modifier, action, and mouse-button enums, lists 3 direct classes, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.time](docs/project-structure/packages/com/spinyowl/spinygui/core/time/README.md) - This reference describes Time service abstraction for animation and frame timing, lists 1 direct class, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.util](docs/project-structure/packages/com/spinyowl/spinygui/core/util/README.md) - This reference describes Small utilities for class-key maps, IO, node visibility, references, and text handling, lists 7 direct classes, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.demo](docs/project-structure/packages/com/spinyowl/spinygui/demo/README.md) - This reference describes Demo namespace folder for runnable examples, lists 0 direct classes, and aggregates 2 descendant packages. +- [com.spinyowl.spinygui.demo.complex](docs/project-structure/packages/com/spinyowl/spinygui/demo/complex/README.md) - This reference describes Windowed GLFW/LWJGL demo framework and concrete NanoVG demo, lists 2 direct classes, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.demo.simple](docs/project-structure/packages/com/spinyowl/spinygui/demo/simple/README.md) - This reference describes Simple demo entry points, lists 2 direct classes, and aggregates 0 descendant packages. ## Reading Order diff --git a/docs/project-structure/README.md b/docs/project-structure/README.md index ba16fc86..8b76cfda 100644 --- a/docs/project-structure/README.md +++ b/docs/project-structure/README.md @@ -2,69 +2,69 @@ This folder mirrors Java package documentation generated from the project source tree. -- Root overview: [`PROJECT_STRUCTURE.md`](../../PROJECT_STRUCTURE.md) -- Agent style/principles: [`AGENTS_CODE_STYLE.md`](../../AGENTS_CODE_STYLE.md) +- [`PROJECT_STRUCTURE.md`](../../PROJECT_STRUCTURE.md) - This reference gives the root-level overview of Gradle modules, main subsystems, and the full package index. +- [`AGENTS_CODE_STYLE.md`](../../AGENTS_CODE_STYLE.md) - This reference describes the coding style, architecture principles, package conventions, and caution areas for future agents. ## Packages by Depth -- [com.spinyowl.spinygui.core.backend.renderer.lwjgl.nanovg.util](packages/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/util/README.md) -- [com.spinyowl.spinygui.core.backend.renderer.lwjgl.nanovg](packages/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/README.md) -- [com.spinyowl.spinygui.core.parser.impl.css.antlr](packages/com/spinyowl/spinygui/core/parser/impl/css/antlr/README.md) -- [com.spinyowl.spinygui.core.parser.impl.css.visitor](packages/com/spinyowl/spinygui/core/parser/impl/css/visitor/README.md) -- [com.spinyowl.spinygui.core.style.stylesheet.selector.combinator](packages/com/spinyowl/spinygui/core/style/stylesheet/selector/combinator/README.md) -- [com.spinyowl.spinygui.core.style.stylesheet.selector.pseudoclass](packages/com/spinyowl/spinygui/core/style/stylesheet/selector/pseudoclass/README.md) -- [com.spinyowl.spinygui.core.style.stylesheet.selector.pseudoelement](packages/com/spinyowl/spinygui/core/style/stylesheet/selector/pseudoelement/README.md) -- [com.spinyowl.spinygui.core.style.stylesheet.selector.simple](packages/com/spinyowl/spinygui/core/style/stylesheet/selector/simple/README.md) -- [com.spinyowl.spinygui.core.backend.renderer.lwjgl](packages/com/spinyowl/spinygui/core/backend/renderer/lwjgl/README.md) -- [com.spinyowl.spinygui.core.parser.impl.css](packages/com/spinyowl/spinygui/core/parser/impl/css/README.md) -- [com.spinyowl.spinygui.core.style.stylesheet.annotation](packages/com/spinyowl/spinygui/core/style/stylesheet/annotation/README.md) -- [com.spinyowl.spinygui.core.style.stylesheet.atrule](packages/com/spinyowl/spinygui/core/style/stylesheet/atrule/README.md) -- [com.spinyowl.spinygui.core.style.stylesheet.impl](packages/com/spinyowl/spinygui/core/style/stylesheet/impl/README.md) -- [com.spinyowl.spinygui.core.style.stylesheet.property](packages/com/spinyowl/spinygui/core/style/stylesheet/property/README.md) -- [com.spinyowl.spinygui.core.style.stylesheet.selector](packages/com/spinyowl/spinygui/core/style/stylesheet/selector/README.md) -- [com.spinyowl.spinygui.core.style.stylesheet.term](packages/com/spinyowl/spinygui/core/style/stylesheet/term/README.md) -- [com.spinyowl.spinygui.core.style.stylesheet.util](packages/com/spinyowl/spinygui/core/style/stylesheet/util/README.md) -- [com.spinyowl.spinygui.core.style.types.background](packages/com/spinyowl/spinygui/core/style/types/background/README.md) -- [com.spinyowl.spinygui.core.style.types.border](packages/com/spinyowl/spinygui/core/style/types/border/README.md) -- [com.spinyowl.spinygui.core.style.types.flex](packages/com/spinyowl/spinygui/core/style/types/flex/README.md) -- [com.spinyowl.spinygui.core.style.types.length](packages/com/spinyowl/spinygui/core/style/types/length/README.md) -- [com.spinyowl.spinygui.core.system.event.listener](packages/com/spinyowl/spinygui/core/system/event/listener/README.md) -- [com.spinyowl.spinygui.core.system.event.processor](packages/com/spinyowl/spinygui/core/system/event/processor/README.md) -- [com.spinyowl.spinygui.core.system.event.provider](packages/com/spinyowl/spinygui/core/system/event/provider/README.md) -- [com.spinyowl.spinygui.core.system.font.impl](packages/com/spinyowl/spinygui/core/system/font/impl/README.md) -- [com.spinyowl.spinygui.core.backend.renderer](packages/com/spinyowl/spinygui/core/backend/renderer/README.md) -- [com.spinyowl.spinygui.core.event.listener](packages/com/spinyowl/spinygui/core/event/listener/README.md) -- [com.spinyowl.spinygui.core.event.processor](packages/com/spinyowl/spinygui/core/event/processor/README.md) -- [com.spinyowl.spinygui.core.input.impl](packages/com/spinyowl/spinygui/core/input/impl/README.md) -- [com.spinyowl.spinygui.core.layout.impl](packages/com/spinyowl/spinygui/core/layout/impl/README.md) -- [com.spinyowl.spinygui.core.node.intersection](packages/com/spinyowl/spinygui/core/node/intersection/README.md) -- [com.spinyowl.spinygui.core.node.layout](packages/com/spinyowl/spinygui/core/node/layout/README.md) -- [com.spinyowl.spinygui.core.parser.impl](packages/com/spinyowl/spinygui/core/parser/impl/README.md) -- [com.spinyowl.spinygui.core.style.manager](packages/com/spinyowl/spinygui/core/style/manager/README.md) -- [com.spinyowl.spinygui.core.style.stylesheet](packages/com/spinyowl/spinygui/core/style/stylesheet/README.md) -- [com.spinyowl.spinygui.core.style.types](packages/com/spinyowl/spinygui/core/style/types/README.md) -- [com.spinyowl.spinygui.core.system.event](packages/com/spinyowl/spinygui/core/system/event/README.md) -- [com.spinyowl.spinygui.core.system.font](packages/com/spinyowl/spinygui/core/system/font/README.md) -- [com.spinyowl.spinygui.core.system.input](packages/com/spinyowl/spinygui/core/system/input/README.md) -- [com.spinyowl.spinygui.core.animation](packages/com/spinyowl/spinygui/core/animation/README.md) -- [com.spinyowl.spinygui.core.backend](packages/com/spinyowl/spinygui/core/backend/README.md) -- [com.spinyowl.spinygui.core.clipboard](packages/com/spinyowl/spinygui/core/clipboard/README.md) -- [com.spinyowl.spinygui.core.cursor](packages/com/spinyowl/spinygui/core/cursor/README.md) -- [com.spinyowl.spinygui.core.event](packages/com/spinyowl/spinygui/core/event/README.md) -- [com.spinyowl.spinygui.core.font](packages/com/spinyowl/spinygui/core/font/README.md) -- [com.spinyowl.spinygui.core.image](packages/com/spinyowl/spinygui/core/image/README.md) -- [com.spinyowl.spinygui.core.input](packages/com/spinyowl/spinygui/core/input/README.md) -- [com.spinyowl.spinygui.core.layout](packages/com/spinyowl/spinygui/core/layout/README.md) -- [com.spinyowl.spinygui.core.node](packages/com/spinyowl/spinygui/core/node/README.md) -- [com.spinyowl.spinygui.core.parser](packages/com/spinyowl/spinygui/core/parser/README.md) -- [com.spinyowl.spinygui.core.style](packages/com/spinyowl/spinygui/core/style/README.md) -- [com.spinyowl.spinygui.core.system](packages/com/spinyowl/spinygui/core/system/README.md) -- [com.spinyowl.spinygui.core.time](packages/com/spinyowl/spinygui/core/time/README.md) -- [com.spinyowl.spinygui.core.util](packages/com/spinyowl/spinygui/core/util/README.md) -- [com.spinyowl.spinygui.demo.complex](packages/com/spinyowl/spinygui/demo/complex/README.md) -- [com.spinyowl.spinygui.demo.simple](packages/com/spinyowl/spinygui/demo/simple/README.md) -- [com.spinyowl.spinygui.core](packages/com/spinyowl/spinygui/core/README.md) -- [com.spinyowl.spinygui.demo](packages/com/spinyowl/spinygui/demo/README.md) -- [com.spinyowl.spinygui](packages/com/spinyowl/spinygui/README.md) -- [com.spinyowl](packages/com/spinyowl/README.md) -- [com](packages/com/README.md) \ No newline at end of file +- [com.spinyowl.spinygui.core.backend.renderer.lwjgl.nanovg.util](packages/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/util/README.md) - This reference describes NanoVG drawing and color helpers, lists 3 direct classes, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.backend.renderer.lwjgl.nanovg](packages/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/README.md) - This reference describes NanoVG renderer orchestration and specialized element/text/border renderers, lists 4 direct classes, and aggregates 1 descendant package. +- [com.spinyowl.spinygui.core.parser.impl.css.antlr](packages/com/spinyowl/spinygui/core/parser/impl/css/antlr/README.md) - This reference describes Generated ANTLR CSS3 lexer/parser/listener/visitor artifacts; Regenerate from the grammar instead of hand-editing, lists 6 direct classes, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.parser.impl.css.visitor](packages/com/spinyowl/spinygui/core/parser/impl/css/visitor/README.md) - This reference describes ANTLR visitors that convert CSS parse trees into stylesheet, selector, declaration, and term model objects, lists 8 direct classes, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.style.stylesheet.selector.combinator](packages/com/spinyowl/spinygui/core/style/stylesheet/selector/combinator/README.md) - This reference describes Combinator selectors for descendant, child, sibling, adjacent sibling, and compound matching, lists 5 direct classes, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.style.stylesheet.selector.pseudoclass](packages/com/spinyowl/spinygui/core/style/stylesheet/selector/pseudoclass/README.md) - This reference describes Pseudo-class selector implementations, lists 1 direct class, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.style.stylesheet.selector.pseudoelement](packages/com/spinyowl/spinygui/core/style/stylesheet/selector/pseudoelement/README.md) - This reference describes Pseudo-element selector implementations, lists 3 direct classes, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.style.stylesheet.selector.simple](packages/com/spinyowl/spinygui/core/style/stylesheet/selector/simple/README.md) - This reference describes Simple selectors for all, element, class, and id matching, lists 4 direct classes, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.backend.renderer.lwjgl](packages/com/spinyowl/spinygui/core/backend/renderer/lwjgl/README.md) - This reference describes Package for lwjgl related classes, lists 0 direct classes, and aggregates 2 descendant packages. +- [com.spinyowl.spinygui.core.parser.impl.css](packages/com/spinyowl/spinygui/core/parser/impl/css/README.md) - This reference describes CSS parser namespace containing generated ANTLR artifacts and handwritten semantic visitors, lists 0 direct classes, and aggregates 2 descendant packages. +- [com.spinyowl.spinygui.core.style.stylesheet.annotation](packages/com/spinyowl/spinygui/core/style/stylesheet/annotation/README.md) - This reference describes Annotations used by stylesheet property providers, lists 1 direct class, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.style.stylesheet.atrule](packages/com/spinyowl/spinygui/core/style/stylesheet/atrule/README.md) - This reference describes CSS at-rule model objects, lists 1 direct class, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.style.stylesheet.impl](packages/com/spinyowl/spinygui/core/style/stylesheet/impl/README.md) - This reference describes Default property-store implementation and provider scanner integration, lists 2 direct classes, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.style.stylesheet.property](packages/com/spinyowl/spinygui/core/style/stylesheet/property/README.md) - This reference describes CSS property providers that parse declarations into typed style values, lists 21 direct classes, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.style.stylesheet.selector](packages/com/spinyowl/spinygui/core/style/stylesheet/selector/README.md) - This reference describes Selector contracts and base selector types, lists 4 direct classes, and aggregates 4 descendant packages. +- [com.spinyowl.spinygui.core.style.stylesheet.term](packages/com/spinyowl/spinygui/core/style/stylesheet/term/README.md) - This reference describes Typed CSS term values produced by parser visitors, lists 9 direct classes, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.style.stylesheet.util](packages/com/spinyowl/spinygui/core/style/stylesheet/util/README.md) - This reference describes Utility functions for converting and validating stylesheet values, lists 1 direct class, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.style.types.background](packages/com/spinyowl/spinygui/core/style/types/background/README.md) - This reference describes Background-origin, repeat, and sizing value objects, lists 3 direct classes, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.style.types.border](packages/com/spinyowl/spinygui/core/style/types/border/README.md) - This reference describes Border item and border-style value objects, lists 2 direct classes, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.style.types.flex](packages/com/spinyowl/spinygui/core/style/types/flex/README.md) - This reference describes Flexbox alignment, direction, wrapping, and justification value constants, lists 6 direct classes, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.style.types.length](packages/com/spinyowl/spinygui/core/style/types/length/README.md) - This reference describes CSS length units, length wrappers, and conversion contract, lists 3 direct classes, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.system.event.listener](packages/com/spinyowl/spinygui/core/system/event/listener/README.md) - This reference describes Adapters that translate raw system events into core event processing and state changes, lists 27 direct classes, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.system.event.processor](packages/com/spinyowl/spinygui/core/system/event/processor/README.md) - This reference describes System-event processor contract and implementation for dispatching platform events, lists 2 direct classes, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.system.event.provider](packages/com/spinyowl/spinygui/core/system/event/provider/README.md) - This reference describes Provider for mapping raw system event classes to listener instances, lists 2 direct classes, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.system.font.impl](packages/com/spinyowl/spinygui/core/system/font/impl/README.md) - This reference describes Default font service, storage, and platform-specific font directory discovery, lists 3 direct classes, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.backend.renderer](packages/com/spinyowl/spinygui/core/backend/renderer/README.md) - This reference describes Renderer SPI consumed by backend implementations, lists 1 direct class, and aggregates 3 descendant packages. +- [com.spinyowl.spinygui.core.event.listener](packages/com/spinyowl/spinygui/core/event/listener/README.md) - This reference describes Generic event listener contract for application events, lists 1 direct class, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.event.processor](packages/com/spinyowl/spinygui/core/event/processor/README.md) - This reference describes Dispatch logic for routing application events to node listeners, lists 2 direct classes, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.input.impl](packages/com/spinyowl/spinygui/core/input/impl/README.md) - This reference describes Default mutable implementations of input services, lists 3 direct classes, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.layout.impl](packages/com/spinyowl/spinygui/core/layout/impl/README.md) - This reference describes Concrete layout algorithms and utilities for block, flex, none, text, and layout tree updates, lists 7 direct classes, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.node.intersection](packages/com/spinyowl/spinygui/core/node/intersection/README.md) - This reference describes Hit-testing strategy objects for node intersection checks, lists 3 direct classes, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.node.layout](packages/com/spinyowl/spinygui/core/node/layout/README.md) - This reference describes Box-model geometry value objects used by layout and rendering, lists 3 direct classes, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.parser.impl](packages/com/spinyowl/spinygui/core/parser/impl/README.md) - This reference describes Default parser implementations and parser factory code, lists 4 direct classes, and aggregates 3 descendant packages. +- [com.spinyowl.spinygui.core.style.manager](packages/com/spinyowl/spinygui/core/style/manager/README.md) - This reference describes Style manager contract and implementation for applying stylesheets to node trees, lists 2 direct classes, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.style.stylesheet](packages/com/spinyowl/spinygui/core/style/stylesheet/README.md) - This reference describes CSS stylesheet domain model: properties, rulesets, declarations, terms, specificity, and provider registry, lists 12 direct classes, and aggregates 11 descendant packages. +- [com.spinyowl.spinygui.core.style.types](packages/com/spinyowl/spinygui/core/style/types/README.md) - This reference describes Typed CSS value objects and constants for non-nested style domains, lists 10 direct classes, and aggregates 4 descendant packages. +- [com.spinyowl.spinygui.core.system.event](packages/com/spinyowl/spinygui/core/system/event/README.md) - This reference describes Raw platform/window/input events before conversion into application-level events, lists 16 direct classes, and aggregates 3 descendant packages. +- [com.spinyowl.spinygui.core.system.font](packages/com/spinyowl/spinygui/core/system/font/README.md) - This reference describes Platform font loading, text metrics, and font storage abstractions, lists 7 direct classes, and aggregates 1 descendant package. +- [com.spinyowl.spinygui.core.system.input](packages/com/spinyowl/spinygui/core/system/input/README.md) - This reference describes Platform-facing key, modifier, action, and mouse-button enums, lists 3 direct classes, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.animation](packages/com/spinyowl/spinygui/core/animation/README.md) - This reference describes Frame-time animation contracts and a simple animator loop, lists 3 direct classes, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.backend](packages/com/spinyowl/spinygui/core/backend/README.md) - This reference describes Backend namespace folder for renderer APIs and implementations, lists 0 direct classes, and aggregates 4 descendant packages. +- [com.spinyowl.spinygui.core.clipboard](packages/com/spinyowl/spinygui/core/clipboard/README.md) - This reference describes Clipboard abstraction used by platform integrations, lists 1 direct class, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.cursor](packages/com/spinyowl/spinygui/core/cursor/README.md) - This reference describes Cursor model and cursor service abstraction, lists 3 direct classes, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.event](packages/com/spinyowl/spinygui/core/event/README.md) - This reference describes Application-level events emitted to nodes and event targets, lists 21 direct classes, and aggregates 2 descendant packages. +- [com.spinyowl.spinygui.core.font](packages/com/spinyowl/spinygui/core/font/README.md) - This reference describes CSS-like font value objects: family, size, stretch, style, and weight, lists 5 direct classes, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.image](packages/com/spinyowl/spinygui/core/image/README.md) - This reference describes Image abstraction used by style and rendering layers, lists 1 direct class, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.input](packages/com/spinyowl/spinygui/core/input/README.md) - This reference describes Input domain model for keyboard, mouse, shortcuts, and user-facing key mappings, lists 10 direct classes, and aggregates 1 descendant package. +- [com.spinyowl.spinygui.core.layout](packages/com/spinyowl/spinygui/core/layout/README.md) - This reference describes Layout contracts, layout context, and text/element layout interfaces, lists 5 direct classes, and aggregates 1 descendant package. +- [com.spinyowl.spinygui.core.node](packages/com/spinyowl/spinygui/core/node/README.md) - This reference describes DOM-like node hierarchy: frames, elements, empty elements, text nodes, and builders, lists 6 direct classes, and aggregates 2 descendant packages. +- [com.spinyowl.spinygui.core.parser](packages/com/spinyowl/spinygui/core/parser/README.md) - This reference describes Parser interfaces for HTML-like node trees and stylesheets, lists 2 direct classes, and aggregates 4 descendant packages. +- [com.spinyowl.spinygui.core.style](packages/com/spinyowl/spinygui/core/style/README.md) - This reference describes Resolved style state applied to nodes after rule matching and property conversion, lists 1 direct class, and aggregates 18 descendant packages. +- [com.spinyowl.spinygui.core.system](packages/com/spinyowl/spinygui/core/system/README.md) - This reference describes Package for system related classes, lists 0 direct classes, and aggregates 7 descendant packages. +- [com.spinyowl.spinygui.core.time](packages/com/spinyowl/spinygui/core/time/README.md) - This reference describes Time service abstraction for animation and frame timing, lists 1 direct class, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.util](packages/com/spinyowl/spinygui/core/util/README.md) - This reference describes Small utilities for class-key maps, IO, node visibility, references, and text handling, lists 7 direct classes, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.demo.complex](packages/com/spinyowl/spinygui/demo/complex/README.md) - This reference describes Windowed GLFW/LWJGL demo framework and concrete NanoVG demo, lists 2 direct classes, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.demo.simple](packages/com/spinyowl/spinygui/demo/simple/README.md) - This reference describes Simple demo entry points, lists 2 direct classes, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core](packages/com/spinyowl/spinygui/core/README.md) - This reference describes Top-level core configuration and shared entry points for the GUI engine, lists 1 direct class, and aggregates 54 descendant packages. +- [com.spinyowl.spinygui.demo](packages/com/spinyowl/spinygui/demo/README.md) - This reference describes Demo namespace folder for runnable examples, lists 0 direct classes, and aggregates 2 descendant packages. +- [com.spinyowl.spinygui](packages/com/spinyowl/spinygui/README.md) - This reference describes SpinyGUI namespace folder aggregating core, backend, and demo packages, lists 0 direct classes, and aggregates 58 descendant packages. +- [com.spinyowl](packages/com/spinyowl/README.md) - This reference describes SpinyOwl namespace folder, lists 0 direct classes, and aggregates 59 descendant packages. +- [com](packages/com/README.md) - This reference describes Top-level Java namespace folder for project packages, lists 0 direct classes, and aggregates 60 descendant packages. \ No newline at end of file diff --git a/docs/project-structure/packages/com/README.md b/docs/project-structure/packages/com/README.md index 8a8db569..e4b7ced4 100644 --- a/docs/project-structure/packages/com/README.md +++ b/docs/project-structure/packages/com/README.md @@ -13,7 +13,7 @@ No direct Java classes were found in this package. ## Child Packages -- [com.spinyowl](spinyowl/README.md) - SpinyOwl namespace folder. +- [com.spinyowl](spinyowl/README.md) - This reference describes SpinyOwl namespace folder, lists 0 direct classes, and aggregates 59 descendant packages. ## Aggregated Contents diff --git a/docs/project-structure/packages/com/spinyowl/README.md b/docs/project-structure/packages/com/spinyowl/README.md index 6861d9e7..b35dc20e 100644 --- a/docs/project-structure/packages/com/spinyowl/README.md +++ b/docs/project-structure/packages/com/spinyowl/README.md @@ -13,7 +13,7 @@ No direct Java classes were found in this package. ## Child Packages -- [com.spinyowl.spinygui](spinygui/README.md) - SpinyGUI namespace folder aggregating core, backend, and demo packages. +- [com.spinyowl.spinygui](spinygui/README.md) - This reference describes SpinyGUI namespace folder aggregating core, backend, and demo packages, lists 0 direct classes, and aggregates 58 descendant packages. ## Aggregated Contents diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/README.md index 4fcbd555..ec20ca37 100644 --- a/docs/project-structure/packages/com/spinyowl/spinygui/README.md +++ b/docs/project-structure/packages/com/spinyowl/spinygui/README.md @@ -13,8 +13,8 @@ No direct Java classes were found in this package. ## Child Packages -- [com.spinyowl.spinygui.core](core/README.md) - Top-level core configuration and shared entry points for the GUI engine. -- [com.spinyowl.spinygui.demo](demo/README.md) - Demo namespace folder for runnable examples. +- [com.spinyowl.spinygui.core](core/README.md) - This reference describes Top-level core configuration and shared entry points for the GUI engine, lists 1 direct class, and aggregates 54 descendant packages. +- [com.spinyowl.spinygui.demo](demo/README.md) - This reference describes Demo namespace folder for runnable examples, lists 0 direct classes, and aggregates 2 descendant packages. ## Aggregated Contents diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/core/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/core/README.md index 08efc21b..4cb213b2 100644 --- a/docs/project-structure/packages/com/spinyowl/spinygui/core/README.md +++ b/docs/project-structure/packages/com/spinyowl/spinygui/core/README.md @@ -18,21 +18,21 @@ Top-level core configuration and shared entry points for the GUI engine. ## Child Packages -- [com.spinyowl.spinygui.core.animation](animation/README.md) - Frame-time animation contracts and a simple animator loop. -- [com.spinyowl.spinygui.core.backend](backend/README.md) - Backend namespace folder for renderer APIs and implementations. -- [com.spinyowl.spinygui.core.clipboard](clipboard/README.md) - Clipboard abstraction used by platform integrations. -- [com.spinyowl.spinygui.core.cursor](cursor/README.md) - Cursor model and cursor service abstraction. -- [com.spinyowl.spinygui.core.event](event/README.md) - Application-level events emitted to nodes and event targets. -- [com.spinyowl.spinygui.core.font](font/README.md) - CSS-like font value objects: family, size, stretch, style, and weight. -- [com.spinyowl.spinygui.core.image](image/README.md) - Image abstraction used by style and rendering layers. -- [com.spinyowl.spinygui.core.input](input/README.md) - Input domain model for keyboard, mouse, shortcuts, and user-facing key mappings. -- [com.spinyowl.spinygui.core.layout](layout/README.md) - Layout contracts, layout context, and text/element layout interfaces. -- [com.spinyowl.spinygui.core.node](node/README.md) - DOM-like node hierarchy: frames, elements, empty elements, text nodes, and builders. -- [com.spinyowl.spinygui.core.parser](parser/README.md) - Parser interfaces for HTML-like node trees and stylesheets. -- [com.spinyowl.spinygui.core.style](style/README.md) - Resolved style state applied to nodes after rule matching and property conversion. -- [com.spinyowl.spinygui.core.system](system/README.md) - Package for system related classes. -- [com.spinyowl.spinygui.core.time](time/README.md) - Time service abstraction for animation and frame timing. -- [com.spinyowl.spinygui.core.util](util/README.md) - Small utilities for class-key maps, IO, node visibility, references, and text handling. +- [com.spinyowl.spinygui.core.animation](animation/README.md) - This reference describes Frame-time animation contracts and a simple animator loop, lists 3 direct classes, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.backend](backend/README.md) - This reference describes Backend namespace folder for renderer APIs and implementations, lists 0 direct classes, and aggregates 4 descendant packages. +- [com.spinyowl.spinygui.core.clipboard](clipboard/README.md) - This reference describes Clipboard abstraction used by platform integrations, lists 1 direct class, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.cursor](cursor/README.md) - This reference describes Cursor model and cursor service abstraction, lists 3 direct classes, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.event](event/README.md) - This reference describes Application-level events emitted to nodes and event targets, lists 21 direct classes, and aggregates 2 descendant packages. +- [com.spinyowl.spinygui.core.font](font/README.md) - This reference describes CSS-like font value objects: family, size, stretch, style, and weight, lists 5 direct classes, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.image](image/README.md) - This reference describes Image abstraction used by style and rendering layers, lists 1 direct class, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.input](input/README.md) - This reference describes Input domain model for keyboard, mouse, shortcuts, and user-facing key mappings, lists 10 direct classes, and aggregates 1 descendant package. +- [com.spinyowl.spinygui.core.layout](layout/README.md) - This reference describes Layout contracts, layout context, and text/element layout interfaces, lists 5 direct classes, and aggregates 1 descendant package. +- [com.spinyowl.spinygui.core.node](node/README.md) - This reference describes DOM-like node hierarchy: frames, elements, empty elements, text nodes, and builders, lists 6 direct classes, and aggregates 2 descendant packages. +- [com.spinyowl.spinygui.core.parser](parser/README.md) - This reference describes Parser interfaces for HTML-like node trees and stylesheets, lists 2 direct classes, and aggregates 4 descendant packages. +- [com.spinyowl.spinygui.core.style](style/README.md) - This reference describes Resolved style state applied to nodes after rule matching and property conversion, lists 1 direct class, and aggregates 18 descendant packages. +- [com.spinyowl.spinygui.core.system](system/README.md) - This reference describes Package for system related classes, lists 0 direct classes, and aggregates 7 descendant packages. +- [com.spinyowl.spinygui.core.time](time/README.md) - This reference describes Time service abstraction for animation and frame timing, lists 1 direct class, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.util](util/README.md) - This reference describes Small utilities for class-key maps, IO, node visibility, references, and text handling, lists 7 direct classes, and aggregates 0 descendant packages. ## Aggregated Contents diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/core/backend/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/core/backend/README.md index f6936e25..54b3d298 100644 --- a/docs/project-structure/packages/com/spinyowl/spinygui/core/backend/README.md +++ b/docs/project-structure/packages/com/spinyowl/spinygui/core/backend/README.md @@ -13,7 +13,7 @@ No direct Java classes were found in this package. ## Child Packages -- [com.spinyowl.spinygui.core.backend.renderer](renderer/README.md) - Renderer SPI consumed by backend implementations. +- [com.spinyowl.spinygui.core.backend.renderer](renderer/README.md) - This reference describes Renderer SPI consumed by backend implementations, lists 1 direct class, and aggregates 3 descendant packages. ## Aggregated Contents diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/core/backend/renderer/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/core/backend/renderer/README.md index c80041f0..c135e287 100644 --- a/docs/project-structure/packages/com/spinyowl/spinygui/core/backend/renderer/README.md +++ b/docs/project-structure/packages/com/spinyowl/spinygui/core/backend/renderer/README.md @@ -18,7 +18,7 @@ Renderer SPI consumed by backend implementations. ## Child Packages -- [com.spinyowl.spinygui.core.backend.renderer.lwjgl](lwjgl/README.md) - Package for lwjgl related classes. +- [com.spinyowl.spinygui.core.backend.renderer.lwjgl](lwjgl/README.md) - This reference describes Package for lwjgl related classes, lists 0 direct classes, and aggregates 2 descendant packages. ## Aggregated Contents diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/core/backend/renderer/lwjgl/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/core/backend/renderer/lwjgl/README.md index 65f4abee..439cf600 100644 --- a/docs/project-structure/packages/com/spinyowl/spinygui/core/backend/renderer/lwjgl/README.md +++ b/docs/project-structure/packages/com/spinyowl/spinygui/core/backend/renderer/lwjgl/README.md @@ -13,7 +13,7 @@ No direct Java classes were found in this package. ## Child Packages -- [com.spinyowl.spinygui.core.backend.renderer.lwjgl.nanovg](nanovg/README.md) - NanoVG renderer orchestration and specialized element/text/border renderers. +- [com.spinyowl.spinygui.core.backend.renderer.lwjgl.nanovg](nanovg/README.md) - This reference describes NanoVG renderer orchestration and specialized element/text/border renderers, lists 4 direct classes, and aggregates 1 descendant package. ## Aggregated Contents diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/README.md index c4661883..d687a7d6 100644 --- a/docs/project-structure/packages/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/README.md +++ b/docs/project-structure/packages/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/README.md @@ -39,7 +39,7 @@ NanoVG renderer orchestration and specialized element/text/border renderers. ## Child Packages -- [com.spinyowl.spinygui.core.backend.renderer.lwjgl.nanovg.util](util/README.md) - NanoVG drawing and color helpers. +- [com.spinyowl.spinygui.core.backend.renderer.lwjgl.nanovg.util](util/README.md) - This reference describes NanoVG drawing and color helpers, lists 3 direct classes, and aggregates 0 descendant packages. ## Aggregated Contents diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/core/event/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/core/event/README.md index 8272a692..208b981f 100644 --- a/docs/project-structure/packages/com/spinyowl/spinygui/core/event/README.md +++ b/docs/project-structure/packages/com/spinyowl/spinygui/core/event/README.md @@ -158,8 +158,8 @@ Application-level events emitted to nodes and event targets. ## Child Packages -- [com.spinyowl.spinygui.core.event.listener](listener/README.md) - Generic event listener contract for application events. -- [com.spinyowl.spinygui.core.event.processor](processor/README.md) - Dispatch logic for routing application events to node listeners. +- [com.spinyowl.spinygui.core.event.listener](listener/README.md) - This reference describes Generic event listener contract for application events, lists 1 direct class, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.event.processor](processor/README.md) - This reference describes Dispatch logic for routing application events to node listeners, lists 2 direct classes, and aggregates 0 descendant packages. ## Aggregated Contents diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/core/input/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/core/input/README.md index be94285a..b96d0c1e 100644 --- a/docs/project-structure/packages/com/spinyowl/spinygui/core/input/README.md +++ b/docs/project-structure/packages/com/spinyowl/spinygui/core/input/README.md @@ -81,7 +81,7 @@ Input domain model for keyboard, mouse, shortcuts, and user-facing key mappings. ## Child Packages -- [com.spinyowl.spinygui.core.input.impl](impl/README.md) - Default mutable implementations of input services. +- [com.spinyowl.spinygui.core.input.impl](impl/README.md) - This reference describes Default mutable implementations of input services, lists 3 direct classes, and aggregates 0 descendant packages. ## Aggregated Contents diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/core/layout/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/core/layout/README.md index b99f026b..73258f75 100644 --- a/docs/project-structure/packages/com/spinyowl/spinygui/core/layout/README.md +++ b/docs/project-structure/packages/com/spinyowl/spinygui/core/layout/README.md @@ -46,7 +46,7 @@ Layout contracts, layout context, and text/element layout interfaces. ## Child Packages -- [com.spinyowl.spinygui.core.layout.impl](impl/README.md) - Concrete layout algorithms and utilities for block, flex, none, text, and layout tree updates. +- [com.spinyowl.spinygui.core.layout.impl](impl/README.md) - This reference describes Concrete layout algorithms and utilities for block, flex, none, text, and layout tree updates, lists 7 direct classes, and aggregates 0 descendant packages. ## Aggregated Contents diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/core/node/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/core/node/README.md index a1ca0d18..ce5c9d57 100644 --- a/docs/project-structure/packages/com/spinyowl/spinygui/core/node/README.md +++ b/docs/project-structure/packages/com/spinyowl/spinygui/core/node/README.md @@ -53,8 +53,8 @@ DOM-like node hierarchy: frames, elements, empty elements, text nodes, and build ## Child Packages -- [com.spinyowl.spinygui.core.node.intersection](intersection/README.md) - Hit-testing strategy objects for node intersection checks. -- [com.spinyowl.spinygui.core.node.layout](layout/README.md) - Box-model geometry value objects used by layout and rendering. +- [com.spinyowl.spinygui.core.node.intersection](intersection/README.md) - This reference describes Hit-testing strategy objects for node intersection checks, lists 3 direct classes, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.node.layout](layout/README.md) - This reference describes Box-model geometry value objects used by layout and rendering, lists 3 direct classes, and aggregates 0 descendant packages. ## Aggregated Contents diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/core/parser/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/core/parser/README.md index e39da4e4..0e67a6f9 100644 --- a/docs/project-structure/packages/com/spinyowl/spinygui/core/parser/README.md +++ b/docs/project-structure/packages/com/spinyowl/spinygui/core/parser/README.md @@ -25,7 +25,7 @@ Parser interfaces for HTML-like node trees and stylesheets. ## Child Packages -- [com.spinyowl.spinygui.core.parser.impl](impl/README.md) - Default parser implementations and parser factory code. +- [com.spinyowl.spinygui.core.parser.impl](impl/README.md) - This reference describes Default parser implementations and parser factory code, lists 4 direct classes, and aggregates 3 descendant packages. ## Aggregated Contents diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/core/parser/impl/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/core/parser/impl/README.md index c936e200..26ccc7d1 100644 --- a/docs/project-structure/packages/com/spinyowl/spinygui/core/parser/impl/README.md +++ b/docs/project-structure/packages/com/spinyowl/spinygui/core/parser/impl/README.md @@ -39,7 +39,7 @@ Default parser implementations and parser factory code. ## Child Packages -- [com.spinyowl.spinygui.core.parser.impl.css](css/README.md) - CSS parser namespace containing generated ANTLR artifacts and handwritten semantic visitors. +- [com.spinyowl.spinygui.core.parser.impl.css](css/README.md) - This reference describes CSS parser namespace containing generated ANTLR artifacts and handwritten semantic visitors, lists 0 direct classes, and aggregates 2 descendant packages. ## Aggregated Contents diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/core/parser/impl/css/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/core/parser/impl/css/README.md index fd15e3b6..67a1ea5a 100644 --- a/docs/project-structure/packages/com/spinyowl/spinygui/core/parser/impl/css/README.md +++ b/docs/project-structure/packages/com/spinyowl/spinygui/core/parser/impl/css/README.md @@ -13,8 +13,8 @@ No direct Java classes were found in this package. ## Child Packages -- [com.spinyowl.spinygui.core.parser.impl.css.antlr](antlr/README.md) - Generated ANTLR CSS3 lexer/parser/listener/visitor artifacts. Regenerate from the grammar instead of hand-editing. -- [com.spinyowl.spinygui.core.parser.impl.css.visitor](visitor/README.md) - ANTLR visitors that convert CSS parse trees into stylesheet, selector, declaration, and term model objects. +- [com.spinyowl.spinygui.core.parser.impl.css.antlr](antlr/README.md) - This reference describes Generated ANTLR CSS3 lexer/parser/listener/visitor artifacts; Regenerate from the grammar instead of hand-editing, lists 6 direct classes, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.parser.impl.css.visitor](visitor/README.md) - This reference describes ANTLR visitors that convert CSS parse trees into stylesheet, selector, declaration, and term model objects, lists 8 direct classes, and aggregates 0 descendant packages. ## Aggregated Contents diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/core/style/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/core/style/README.md index 9dfe966f..1ec044e3 100644 --- a/docs/project-structure/packages/com/spinyowl/spinygui/core/style/README.md +++ b/docs/project-structure/packages/com/spinyowl/spinygui/core/style/README.md @@ -18,9 +18,9 @@ Resolved style state applied to nodes after rule matching and property conversio ## Child Packages -- [com.spinyowl.spinygui.core.style.manager](manager/README.md) - Style manager contract and implementation for applying stylesheets to node trees. -- [com.spinyowl.spinygui.core.style.stylesheet](stylesheet/README.md) - CSS stylesheet domain model: properties, rulesets, declarations, terms, specificity, and provider registry. -- [com.spinyowl.spinygui.core.style.types](types/README.md) - Typed CSS value objects and constants for non-nested style domains. +- [com.spinyowl.spinygui.core.style.manager](manager/README.md) - This reference describes Style manager contract and implementation for applying stylesheets to node trees, lists 2 direct classes, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.style.stylesheet](stylesheet/README.md) - This reference describes CSS stylesheet domain model: properties, rulesets, declarations, terms, specificity, and provider registry, lists 12 direct classes, and aggregates 11 descendant packages. +- [com.spinyowl.spinygui.core.style.types](types/README.md) - This reference describes Typed CSS value objects and constants for non-nested style domains, lists 10 direct classes, and aggregates 4 descendant packages. ## Aggregated Contents diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/README.md index 3be4b1f5..16ef2eeb 100644 --- a/docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/README.md +++ b/docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/README.md @@ -95,13 +95,13 @@ CSS stylesheet domain model: properties, rulesets, declarations, terms, specific ## Child Packages -- [com.spinyowl.spinygui.core.style.stylesheet.annotation](annotation/README.md) - Annotations used by stylesheet property providers. -- [com.spinyowl.spinygui.core.style.stylesheet.atrule](atrule/README.md) - CSS at-rule model objects. -- [com.spinyowl.spinygui.core.style.stylesheet.impl](impl/README.md) - Default property-store implementation and provider scanner integration. -- [com.spinyowl.spinygui.core.style.stylesheet.property](property/README.md) - CSS property providers that parse declarations into typed style values. -- [com.spinyowl.spinygui.core.style.stylesheet.selector](selector/README.md) - Selector contracts and base selector types. -- [com.spinyowl.spinygui.core.style.stylesheet.term](term/README.md) - Typed CSS term values produced by parser visitors. -- [com.spinyowl.spinygui.core.style.stylesheet.util](util/README.md) - Utility functions for converting and validating stylesheet values. +- [com.spinyowl.spinygui.core.style.stylesheet.annotation](annotation/README.md) - This reference describes Annotations used by stylesheet property providers, lists 1 direct class, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.style.stylesheet.atrule](atrule/README.md) - This reference describes CSS at-rule model objects, lists 1 direct class, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.style.stylesheet.impl](impl/README.md) - This reference describes Default property-store implementation and provider scanner integration, lists 2 direct classes, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.style.stylesheet.property](property/README.md) - This reference describes CSS property providers that parse declarations into typed style values, lists 21 direct classes, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.style.stylesheet.selector](selector/README.md) - This reference describes Selector contracts and base selector types, lists 4 direct classes, and aggregates 4 descendant packages. +- [com.spinyowl.spinygui.core.style.stylesheet.term](term/README.md) - This reference describes Typed CSS term values produced by parser visitors, lists 9 direct classes, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.style.stylesheet.util](util/README.md) - This reference describes Utility functions for converting and validating stylesheet values, lists 1 direct class, and aggregates 0 descendant packages. ## Aggregated Contents diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/selector/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/selector/README.md index 9d81d0ab..7f78a405 100644 --- a/docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/selector/README.md +++ b/docs/project-structure/packages/com/spinyowl/spinygui/core/style/stylesheet/selector/README.md @@ -39,10 +39,10 @@ Selector contracts and base selector types. ## Child Packages -- [com.spinyowl.spinygui.core.style.stylesheet.selector.combinator](combinator/README.md) - Combinator selectors for descendant, child, sibling, adjacent sibling, and compound matching. -- [com.spinyowl.spinygui.core.style.stylesheet.selector.pseudoclass](pseudoclass/README.md) - Pseudo-class selector implementations. -- [com.spinyowl.spinygui.core.style.stylesheet.selector.pseudoelement](pseudoelement/README.md) - Pseudo-element selector implementations. -- [com.spinyowl.spinygui.core.style.stylesheet.selector.simple](simple/README.md) - Simple selectors for all, element, class, and id matching. +- [com.spinyowl.spinygui.core.style.stylesheet.selector.combinator](combinator/README.md) - This reference describes Combinator selectors for descendant, child, sibling, adjacent sibling, and compound matching, lists 5 direct classes, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.style.stylesheet.selector.pseudoclass](pseudoclass/README.md) - This reference describes Pseudo-class selector implementations, lists 1 direct class, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.style.stylesheet.selector.pseudoelement](pseudoelement/README.md) - This reference describes Pseudo-element selector implementations, lists 3 direct classes, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.style.stylesheet.selector.simple](simple/README.md) - This reference describes Simple selectors for all, element, class, and id matching, lists 4 direct classes, and aggregates 0 descendant packages. ## Aggregated Contents diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/core/style/types/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/core/style/types/README.md index 414d045e..9e12e523 100644 --- a/docs/project-structure/packages/com/spinyowl/spinygui/core/style/types/README.md +++ b/docs/project-structure/packages/com/spinyowl/spinygui/core/style/types/README.md @@ -81,10 +81,10 @@ Typed CSS value objects and constants for non-nested style domains. ## Child Packages -- [com.spinyowl.spinygui.core.style.types.background](background/README.md) - Background-origin, repeat, and sizing value objects. -- [com.spinyowl.spinygui.core.style.types.border](border/README.md) - Border item and border-style value objects. -- [com.spinyowl.spinygui.core.style.types.flex](flex/README.md) - Flexbox alignment, direction, wrapping, and justification value constants. -- [com.spinyowl.spinygui.core.style.types.length](length/README.md) - CSS length units, length wrappers, and conversion contract. +- [com.spinyowl.spinygui.core.style.types.background](background/README.md) - This reference describes Background-origin, repeat, and sizing value objects, lists 3 direct classes, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.style.types.border](border/README.md) - This reference describes Border item and border-style value objects, lists 2 direct classes, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.style.types.flex](flex/README.md) - This reference describes Flexbox alignment, direction, wrapping, and justification value constants, lists 6 direct classes, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.style.types.length](length/README.md) - This reference describes CSS length units, length wrappers, and conversion contract, lists 3 direct classes, and aggregates 0 descendant packages. ## Aggregated Contents diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/core/system/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/core/system/README.md index 0bfb6352..9aad8162 100644 --- a/docs/project-structure/packages/com/spinyowl/spinygui/core/system/README.md +++ b/docs/project-structure/packages/com/spinyowl/spinygui/core/system/README.md @@ -13,9 +13,9 @@ No direct Java classes were found in this package. ## Child Packages -- [com.spinyowl.spinygui.core.system.event](event/README.md) - Raw platform/window/input events before conversion into application-level events. -- [com.spinyowl.spinygui.core.system.font](font/README.md) - Platform font loading, text metrics, and font storage abstractions. -- [com.spinyowl.spinygui.core.system.input](input/README.md) - Platform-facing key, modifier, action, and mouse-button enums. +- [com.spinyowl.spinygui.core.system.event](event/README.md) - This reference describes Raw platform/window/input events before conversion into application-level events, lists 16 direct classes, and aggregates 3 descendant packages. +- [com.spinyowl.spinygui.core.system.font](font/README.md) - This reference describes Platform font loading, text metrics, and font storage abstractions, lists 7 direct classes, and aggregates 1 descendant package. +- [com.spinyowl.spinygui.core.system.input](input/README.md) - This reference describes Platform-facing key, modifier, action, and mouse-button enums, lists 3 direct classes, and aggregates 0 descendant packages. ## Aggregated Contents diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/core/system/event/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/core/system/event/README.md index 20c11405..aabaf791 100644 --- a/docs/project-structure/packages/com/spinyowl/spinygui/core/system/event/README.md +++ b/docs/project-structure/packages/com/spinyowl/spinygui/core/system/event/README.md @@ -123,9 +123,9 @@ Raw platform/window/input events before conversion into application-level events ## Child Packages -- [com.spinyowl.spinygui.core.system.event.listener](listener/README.md) - Adapters that translate raw system events into core event processing and state changes. -- [com.spinyowl.spinygui.core.system.event.processor](processor/README.md) - System-event processor contract and implementation for dispatching platform events. -- [com.spinyowl.spinygui.core.system.event.provider](provider/README.md) - Provider for mapping raw system event classes to listener instances. +- [com.spinyowl.spinygui.core.system.event.listener](listener/README.md) - This reference describes Adapters that translate raw system events into core event processing and state changes, lists 27 direct classes, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.system.event.processor](processor/README.md) - This reference describes System-event processor contract and implementation for dispatching platform events, lists 2 direct classes, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.core.system.event.provider](provider/README.md) - This reference describes Provider for mapping raw system event classes to listener instances, lists 2 direct classes, and aggregates 0 descendant packages. ## Aggregated Contents diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/core/system/font/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/core/system/font/README.md index f154665b..c769ee62 100644 --- a/docs/project-structure/packages/com/spinyowl/spinygui/core/system/font/README.md +++ b/docs/project-structure/packages/com/spinyowl/spinygui/core/system/font/README.md @@ -60,7 +60,7 @@ Platform font loading, text metrics, and font storage abstractions. ## Child Packages -- [com.spinyowl.spinygui.core.system.font.impl](impl/README.md) - Default font service, storage, and platform-specific font directory discovery. +- [com.spinyowl.spinygui.core.system.font.impl](impl/README.md) - This reference describes Default font service, storage, and platform-specific font directory discovery, lists 3 direct classes, and aggregates 0 descendant packages. ## Aggregated Contents diff --git a/docs/project-structure/packages/com/spinyowl/spinygui/demo/README.md b/docs/project-structure/packages/com/spinyowl/spinygui/demo/README.md index 1b0b1cd3..52c30cec 100644 --- a/docs/project-structure/packages/com/spinyowl/spinygui/demo/README.md +++ b/docs/project-structure/packages/com/spinyowl/spinygui/demo/README.md @@ -13,8 +13,8 @@ No direct Java classes were found in this package. ## Child Packages -- [com.spinyowl.spinygui.demo.complex](complex/README.md) - Windowed GLFW/LWJGL demo framework and concrete NanoVG demo. -- [com.spinyowl.spinygui.demo.simple](simple/README.md) - Simple demo entry points. +- [com.spinyowl.spinygui.demo.complex](complex/README.md) - This reference describes Windowed GLFW/LWJGL demo framework and concrete NanoVG demo, lists 2 direct classes, and aggregates 0 descendant packages. +- [com.spinyowl.spinygui.demo.simple](simple/README.md) - This reference describes Simple demo entry points, lists 2 direct classes, and aggregates 0 descendant packages. ## Aggregated Contents diff --git a/tools/generate_structure_docs.js b/tools/generate_structure_docs.js index a36ddc6c..1e873893 100644 --- a/tools/generate_structure_docs.js +++ b/tools/generate_structure_docs.js @@ -260,6 +260,22 @@ function packageDescription(pkg) { return `Package for ${last.replace(/([a-z])([A-Z])/g, "$1 $2").toLowerCase()} related classes.`; } +function packageReferenceSentence(pkg) { + const info = packageMap.get(pkg); + const classCount = info ? info.classes.length : 0; + const descendantCount = info ? descendants(pkg, [...packageMap.keys()]).length : 0; + const classText = classCount === 1 ? "1 direct class" : `${classCount} direct classes`; + const descendantText = + descendantCount === 1 + ? "1 descendant package" + : `${descendantCount} descendant packages`; + const description = packageDescription(pkg) + .replace(/\s+/g, " ") + .replace(/\.\s+/g, "; ") + .replace(/\.$/, ""); + return `This reference describes ${description}, lists ${classText}, and aggregates ${descendantText}.`; +} + function writePackageDoc(pkg, allPackages) { const info = packageMap.get(pkg); const outFile = path.join(packageDir(pkg), "README.md"); @@ -298,7 +314,7 @@ function writePackageDoc(pkg, allPackages) { lines.push("## Child Packages"); lines.push(""); for (const child of children.sort()) { - lines.push(`- [${child}](${linkToPackage(child, outFile)}) - ${packageDescription(child)}`); + lines.push(`- [${child}](${linkToPackage(child, outFile)}) - ${packageReferenceSentence(child)}`); } lines.push(""); } @@ -352,8 +368,7 @@ function writeRootDoc(allPackages) { lines.push("## Package Index"); lines.push(""); for (const pkg of allPackages) { - const info = packageMap.get(pkg); - lines.push(`- [${pkg}](${path.relative(root, path.join(packageDir(pkg), "README.md")).replace(/\\/g, "/")}) - ${info.classes.length} direct class(es); ${packageDescription(pkg)}`); + lines.push(`- [${pkg}](${path.relative(root, path.join(packageDir(pkg), "README.md")).replace(/\\/g, "/")}) - ${packageReferenceSentence(pkg)}`); } lines.push(""); lines.push("## Reading Order"); @@ -370,13 +385,13 @@ function writeIndex(allPackages) { lines.push(""); lines.push("This folder mirrors Java package documentation generated from the project source tree."); lines.push(""); - lines.push("- Root overview: [`PROJECT_STRUCTURE.md`](../../PROJECT_STRUCTURE.md)"); - lines.push("- Agent style/principles: [`AGENTS_CODE_STYLE.md`](../../AGENTS_CODE_STYLE.md)"); + lines.push("- [`PROJECT_STRUCTURE.md`](../../PROJECT_STRUCTURE.md) - This reference gives the root-level overview of Gradle modules, main subsystems, and the full package index."); + lines.push("- [`AGENTS_CODE_STYLE.md`](../../AGENTS_CODE_STYLE.md) - This reference describes the coding style, architecture principles, package conventions, and caution areas for future agents."); lines.push(""); lines.push("## Packages by Depth"); lines.push(""); for (const pkg of [...allPackages].sort((a, b) => packageDepth(b) - packageDepth(a) || a.localeCompare(b))) { - lines.push(`- [${pkg}](${path.relative(path.dirname(file), path.join(packageDir(pkg), "README.md")).replace(/\\/g, "/")})`); + lines.push(`- [${pkg}](${path.relative(path.dirname(file), path.join(packageDir(pkg), "README.md")).replace(/\\/g, "/")}) - ${packageReferenceSentence(pkg)}`); } fs.writeFileSync(file, lines.join("\n"), "utf8"); } From 3719510ee37fa94b5a0158a07d01028e741cd9be Mon Sep 17 00:00:00 2001 From: SpinyOwl Date: Tue, 12 May 2026 02:36:15 +0200 Subject: [PATCH 003/121] Implement browser-like inline text layout --- .../lwjgl/nanovg/NvgTextRenderer.java | 95 +++- .../spinygui/core/layout/FontMetrics.java | 14 + .../spinygui/core/layout/InlineFragment.java | 41 ++ .../spinygui/core/layout/LineBox.java | 50 ++ .../spinygui/core/layout/TextMeasurer.java | 11 + .../core/layout/impl/BlockLayout.java | 34 +- .../layout/impl/FontServiceTextMeasurer.java | 23 + .../layout/impl/InlineFormattingContext.java | 451 ++++++++++++++++++ .../layout/impl/LayoutServiceProvider.java | 4 +- .../com/spinyowl/spinygui/core/node/Text.java | 12 + .../spinygui/core/style/ResolvedStyle.java | 20 + .../core/style/stylesheet/Properties.java | 2 + .../property/TextPropertyProvider.java | 35 ++ .../core/style/types/OverflowWrap.java | 47 ++ .../spinygui/core/style/types/TextAlign.java | 48 ++ .../core/system/font/FontService.java | 11 + .../system/font/impl/FontServiceImpl.java | 32 +- .../core/layout/impl/BlockLayoutTest.java | 139 ++++++ .../impl/InlineFormattingContextTest.java | 226 +++++++++ 19 files changed, 1276 insertions(+), 19 deletions(-) create mode 100644 core/src/main/java/com/spinyowl/spinygui/core/layout/FontMetrics.java create mode 100644 core/src/main/java/com/spinyowl/spinygui/core/layout/InlineFragment.java create mode 100644 core/src/main/java/com/spinyowl/spinygui/core/layout/LineBox.java create mode 100644 core/src/main/java/com/spinyowl/spinygui/core/layout/TextMeasurer.java create mode 100644 core/src/main/java/com/spinyowl/spinygui/core/layout/impl/FontServiceTextMeasurer.java create mode 100644 core/src/main/java/com/spinyowl/spinygui/core/layout/impl/InlineFormattingContext.java create mode 100644 core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/TextPropertyProvider.java create mode 100644 core/src/main/java/com/spinyowl/spinygui/core/style/types/OverflowWrap.java create mode 100644 core/src/main/java/com/spinyowl/spinygui/core/style/types/TextAlign.java create mode 100644 core/src/test/java/com/spinyowl/spinygui/core/layout/impl/BlockLayoutTest.java create mode 100644 core/src/test/java/com/spinyowl/spinygui/core/layout/impl/InlineFormattingContextTest.java diff --git a/core.backend.lwjgl.nanovg/src/main/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/NvgTextRenderer.java b/core.backend.lwjgl.nanovg/src/main/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/NvgTextRenderer.java index 72d8ede3..424d7b25 100644 --- a/core.backend.lwjgl.nanovg/src/main/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/NvgTextRenderer.java +++ b/core.backend.lwjgl.nanovg/src/main/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/NvgTextRenderer.java @@ -2,37 +2,102 @@ import static com.spinyowl.spinygui.core.backend.renderer.lwjgl.nanovg.util.NvgRenderUtils.createScissor; import static com.spinyowl.spinygui.core.backend.renderer.lwjgl.nanovg.util.NvgRenderUtils.resetScissor; -import static com.spinyowl.spinygui.core.backend.renderer.lwjgl.nanovg.util.NvgShapes.drawRect; -import static com.spinyowl.spinygui.core.backend.renderer.lwjgl.nanovg.util.NvgShapes.drawRectStroke; +import static com.spinyowl.spinygui.core.backend.renderer.lwjgl.nanovg.util.NvgColorUtil.create; +import static org.lwjgl.nanovg.NanoVG.NVG_ALIGN_BASELINE; +import static org.lwjgl.nanovg.NanoVG.NVG_ALIGN_LEFT; +import static org.lwjgl.nanovg.NanoVG.nvgCreateFontMem; +import static org.lwjgl.nanovg.NanoVG.nvgFillColor; +import static org.lwjgl.nanovg.NanoVG.nvgFontFace; +import static org.lwjgl.nanovg.NanoVG.nvgFontSize; import static org.lwjgl.nanovg.NanoVG.nvgRestore; import static org.lwjgl.nanovg.NanoVG.nvgSave; +import static org.lwjgl.nanovg.NanoVG.nvgText; +import static org.lwjgl.nanovg.NanoVG.nvgTextAlign; +import static org.lwjgl.system.MemoryUtil.memFree; +import static org.lwjgl.system.MemoryUtil.memUTF8; -import com.spinyowl.spinygui.core.node.Element; +import com.spinyowl.spinygui.core.layout.InlineFragment; import com.spinyowl.spinygui.core.node.Node; import com.spinyowl.spinygui.core.node.Text; -import com.spinyowl.spinygui.core.style.stylesheet.util.StyleUtils; -import com.spinyowl.spinygui.core.style.types.Color; +import com.spinyowl.spinygui.core.font.Font; +import com.spinyowl.spinygui.core.util.IOUtil; +import java.nio.ByteBuffer; +import java.util.HashMap; +import java.util.Map; +import org.joml.Vector2f; public class NvgTextRenderer { + private final Map loadedFontFaces = new HashMap<>(); + private final Map fontBuffers = new HashMap<>(); public void render(Node node, long nanovg) { Text text = node.asText(); - var position = text.absolutePosition(); - var size = text.size(); - - Element parent = text.parent(); - if (parent == null) return; - - Float fontSize = StyleUtils.getFontSize(text); - if (fontSize == null) return; + if (text.inlineFragments().isEmpty()) { + return; + } createScissor(nanovg, node); nvgSave(nanovg); - drawRect(nanovg, position, size, Color.ROYALBLUE, 1); - drawRectStroke(nanovg, position, size, Color.RED, 1); + nvgTextAlign(nanovg, NVG_ALIGN_LEFT | NVG_ALIGN_BASELINE); + Vector2f offset = + text.offsetParent() == null ? new Vector2f() : text.offsetParent().absolutePosition(); + for (InlineFragment fragment : text.inlineFragments()) { + renderFragment(fragment, nanovg, offset); + } nvgRestore(nanovg); resetScissor(nanovg); } + + private void renderFragment(InlineFragment fragment, long nanovg, Vector2f offset) { + if (!fragment.textFragment()) { + return; + } + String fontFace = fontFace(fragment.font(), nanovg); + if (fontFace == null) { + return; + } + nvgFontFace(nanovg, fontFace); + nvgFontSize(nanovg, fragment.fontSize()); + try (var color = create(fragment.color())) { + nvgFillColor(nanovg, color); + ByteBuffer textBuffer = memUTF8(fragment.text(), false); + try { + nvgText(nanovg, offset.x + fragment.x(), offset.y + fragment.baseline(), textBuffer); + } finally { + memFree(textBuffer); + } + } + } + + private String fontFace(Font font, long nanovg) { + String key = + font.fontFamily() + + "|" + + font.style() + + "|" + + font.weight() + + "|" + + font.stretch() + + "|" + + font.path(); + if (loadedFontFaces.containsKey(key)) { + return loadedFontFaces.get(key); + } + + ByteBuffer fontBuffer = fontBuffers.computeIfAbsent(font.path(), IOUtil::resourceAsByteBuffer); + if (fontBuffer == null) { + return null; + } + + String fontFace = font.fontFamily() + "-" + Integer.toUnsignedString(key.hashCode()); + int id = nvgCreateFontMem(nanovg, fontFace, fontBuffer.duplicate(), false); + if (id == -1) { + return null; + } + + loadedFontFaces.put(key, fontFace); + return fontFace; + } } diff --git a/core/src/main/java/com/spinyowl/spinygui/core/layout/FontMetrics.java b/core/src/main/java/com/spinyowl/spinygui/core/layout/FontMetrics.java new file mode 100644 index 00000000..62fdf3c3 --- /dev/null +++ b/core/src/main/java/com/spinyowl/spinygui/core/layout/FontMetrics.java @@ -0,0 +1,14 @@ +package com.spinyowl.spinygui.core.layout; + +import lombok.Value; + +@Value +public class FontMetrics { + float ascent; + float descent; + float lineGap; + + public float lineHeight(float requestedLineHeight) { + return Math.max(requestedLineHeight, ascent + descent + lineGap); + } +} diff --git a/core/src/main/java/com/spinyowl/spinygui/core/layout/InlineFragment.java b/core/src/main/java/com/spinyowl/spinygui/core/layout/InlineFragment.java new file mode 100644 index 00000000..6d7cca05 --- /dev/null +++ b/core/src/main/java/com/spinyowl/spinygui/core/layout/InlineFragment.java @@ -0,0 +1,41 @@ +package com.spinyowl.spinygui.core.layout; + +import com.spinyowl.spinygui.core.font.Font; +import com.spinyowl.spinygui.core.node.Node; +import com.spinyowl.spinygui.core.style.types.Color; +import lombok.Builder; +import lombok.Value; + +@Value +@Builder +public class InlineFragment { + Node node; + String text; + float x; + float y; + float width; + float height; + float baseline; + Font font; + float fontSize; + Color color; + + public boolean textFragment() { + return text != null && !text.isEmpty(); + } + + public InlineFragment translate(float dx, float dy) { + return InlineFragment.builder() + .node(node) + .text(text) + .x(x + dx) + .y(y + dy) + .width(width) + .height(height) + .baseline(baseline + dy) + .font(font) + .fontSize(fontSize) + .color(color) + .build(); + } +} diff --git a/core/src/main/java/com/spinyowl/spinygui/core/layout/LineBox.java b/core/src/main/java/com/spinyowl/spinygui/core/layout/LineBox.java new file mode 100644 index 00000000..79188b11 --- /dev/null +++ b/core/src/main/java/com/spinyowl/spinygui/core/layout/LineBox.java @@ -0,0 +1,50 @@ +package com.spinyowl.spinygui.core.layout; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class LineBox { + private float x; + private float y; + private float width; + private float height; + private float baseline; + private final List fragments = new ArrayList<>(); + + public List fragments() { + return Collections.unmodifiableList(fragments); + } + + public void addFragment(InlineFragment fragment) { + fragments.add(fragment); + width = Math.max(width, fragment.x() + fragment.width() - x); + height = Math.max(height, fragment.height()); + baseline = Math.max(baseline, fragment.baseline() - y); + } + + public void replaceFragments(List newFragments) { + fragments.clear(); + width = 0; + height = 0; + baseline = 0; + newFragments.forEach(this::addFragment); + } + + public void removeLastFragment() { + if (fragments.isEmpty()) { + return; + } + fragments.remove(fragments.size() - 1); + var copy = List.copyOf(fragments); + replaceFragments(copy); + } + + public boolean empty() { + return fragments.isEmpty(); + } +} diff --git a/core/src/main/java/com/spinyowl/spinygui/core/layout/TextMeasurer.java b/core/src/main/java/com/spinyowl/spinygui/core/layout/TextMeasurer.java new file mode 100644 index 00000000..9502afdd --- /dev/null +++ b/core/src/main/java/com/spinyowl/spinygui/core/layout/TextMeasurer.java @@ -0,0 +1,11 @@ +package com.spinyowl.spinygui.core.layout; + +import com.spinyowl.spinygui.core.font.Font; +import lombok.NonNull; + +public interface TextMeasurer { + + float measure(@NonNull String text, @NonNull Font font, float fontSize); + + FontMetrics metrics(@NonNull Font font, float fontSize, float lineHeight); +} diff --git a/core/src/main/java/com/spinyowl/spinygui/core/layout/impl/BlockLayout.java b/core/src/main/java/com/spinyowl/spinygui/core/layout/impl/BlockLayout.java index 8cb9d979..774917c8 100644 --- a/core/src/main/java/com/spinyowl/spinygui/core/layout/impl/BlockLayout.java +++ b/core/src/main/java/com/spinyowl/spinygui/core/layout/impl/BlockLayout.java @@ -12,6 +12,7 @@ import com.spinyowl.spinygui.core.layout.LayoutService; import com.spinyowl.spinygui.core.node.Element; import com.spinyowl.spinygui.core.node.Frame; +import com.spinyowl.spinygui.core.node.Node; import com.spinyowl.spinygui.core.node.layout.Box; import com.spinyowl.spinygui.core.node.layout.Edges; import com.spinyowl.spinygui.core.style.ResolvedStyle; @@ -20,6 +21,8 @@ import com.spinyowl.spinygui.core.style.types.border.BorderStyle; import com.spinyowl.spinygui.core.style.types.length.Length.PixelLength; import com.spinyowl.spinygui.core.style.types.length.Unit; +import java.util.ArrayList; +import java.util.List; import java.util.Optional; import java.util.function.Consumer; import lombok.NonNull; @@ -29,6 +32,7 @@ public class BlockLayout implements ElementLayout { @NonNull private final LayoutService layoutService; + @NonNull private final InlineFormattingContext inlineFormattingContext; @Override public void layout(Element element, LayoutContext context) { @@ -314,7 +318,7 @@ private float childrenHeight( float childrenHeight = 0; Unit height = style.height(); if (!skipChildren) { - layoutService.layoutChildNodes(element, context); + layoutFlowChildren(element); } if (style.display().equals(Display.BLOCK) && height.isAuto() && !skipChildren) { childrenHeight = getChildNodesHeight(element); @@ -363,6 +367,34 @@ private boolean shouldSkip(Element element) { return element.frame() == null || (element.parent() == null && !(element instanceof Frame)); } + private void layoutFlowChildren(Element element) { + LayoutContext context = new LayoutContext(); + List inlineNodes = new ArrayList<>(); + for (Node child : element.childNodes()) { + if (inlineFormattingContext.inlineNode(child)) { + inlineNodes.add(child); + } else { + flushInlineNodes(element, context, inlineNodes); + layoutService.layoutNode(child, context); + } + } + flushInlineNodes(element, context, inlineNodes); + } + + private void flushInlineNodes(Element element, LayoutContext context, List inlineNodes) { + if (inlineNodes.isEmpty()) { + return; + } + float contentStart = element.box().border().top() + element.box().padding().top(); + float startY = + context.lastBlockBottomY() == null + ? 0 + : Math.max(0, context.lastBlockBottomY() - contentStart); + float height = inlineFormattingContext.layout(element, inlineNodes, startY); + context.lastBlockBottomY(contentStart + startY + height); + inlineNodes.clear(); + } + private void applyPadding( PixelLength borderWidth, BorderStyle borderStyle, Consumer borderConsumer) { if (borderWidth != null && !BorderStyle.NONE.equals(borderStyle)) { diff --git a/core/src/main/java/com/spinyowl/spinygui/core/layout/impl/FontServiceTextMeasurer.java b/core/src/main/java/com/spinyowl/spinygui/core/layout/impl/FontServiceTextMeasurer.java new file mode 100644 index 00000000..977125a7 --- /dev/null +++ b/core/src/main/java/com/spinyowl/spinygui/core/layout/impl/FontServiceTextMeasurer.java @@ -0,0 +1,23 @@ +package com.spinyowl.spinygui.core.layout.impl; + +import com.spinyowl.spinygui.core.font.Font; +import com.spinyowl.spinygui.core.layout.FontMetrics; +import com.spinyowl.spinygui.core.layout.TextMeasurer; +import com.spinyowl.spinygui.core.system.font.FontService; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class FontServiceTextMeasurer implements TextMeasurer { + @NonNull private final FontService fontService; + + @Override + public float measure(@NonNull String text, @NonNull Font font, float fontSize) { + return fontService.getTextLineMetrics(text, font, fontSize, 1).width(); + } + + @Override + public FontMetrics metrics(@NonNull Font font, float fontSize, float lineHeight) { + return fontService.getFontMetrics(font, fontSize, lineHeight); + } +} diff --git a/core/src/main/java/com/spinyowl/spinygui/core/layout/impl/InlineFormattingContext.java b/core/src/main/java/com/spinyowl/spinygui/core/layout/impl/InlineFormattingContext.java new file mode 100644 index 00000000..9053ec20 --- /dev/null +++ b/core/src/main/java/com/spinyowl/spinygui/core/layout/impl/InlineFormattingContext.java @@ -0,0 +1,451 @@ +package com.spinyowl.spinygui.core.layout.impl; + +import static com.spinyowl.spinygui.core.layout.impl.LayoutUtils.setBorders; +import static com.spinyowl.spinygui.core.layout.impl.LayoutUtils.setPadding; +import static com.spinyowl.spinygui.core.style.stylesheet.util.StyleUtils.getFloatLengthOptional; +import static com.spinyowl.spinygui.core.style.types.Display.INLINE; +import static com.spinyowl.spinygui.core.style.types.OverflowWrap.BREAK_WORD; +import static com.spinyowl.spinygui.core.style.types.OverflowWrap.ANYWHERE; +import static com.spinyowl.spinygui.core.style.types.TextAlign.CENTER; +import static com.spinyowl.spinygui.core.style.types.TextAlign.RIGHT; +import static com.spinyowl.spinygui.core.style.types.WhiteSpace.NOWRAP; +import static com.spinyowl.spinygui.core.style.types.WhiteSpace.NORMAL; +import static com.spinyowl.spinygui.core.style.types.WhiteSpace.PRE; +import static com.spinyowl.spinygui.core.style.types.WhiteSpace.PRE_LINE; +import static com.spinyowl.spinygui.core.style.types.WhiteSpace.PRE_WRAP; + +import com.spinyowl.spinygui.core.font.Font; +import com.spinyowl.spinygui.core.font.FontStyle; +import com.spinyowl.spinygui.core.font.FontWeight; +import com.spinyowl.spinygui.core.layout.FontMetrics; +import com.spinyowl.spinygui.core.layout.InlineFragment; +import com.spinyowl.spinygui.core.layout.LineBox; +import com.spinyowl.spinygui.core.layout.TextMeasurer; +import com.spinyowl.spinygui.core.node.Element; +import com.spinyowl.spinygui.core.node.Node; +import com.spinyowl.spinygui.core.node.Text; +import com.spinyowl.spinygui.core.style.ResolvedStyle; +import com.spinyowl.spinygui.core.style.stylesheet.util.StyleUtils; +import com.spinyowl.spinygui.core.style.types.OverflowWrap; +import com.spinyowl.spinygui.core.style.types.WhiteSpace; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class InlineFormattingContext { + @NonNull private final TextMeasurer textMeasurer; + + public float layout(@NonNull Element parent, @NonNull List nodes, float startY) { + if (nodes.isEmpty()) { + return 0; + } + + float contentX = parent.box().border().left() + parent.box().padding().left(); + float contentY = parent.box().border().top() + parent.box().padding().top() + startY; + float availableWidth = Math.max(0, parent.box().content().width()); + List units = new ArrayList<>(); + Map> textFragments = new HashMap<>(); + Map> elementFragments = new HashMap<>(); + + nodes.forEach(node -> collectUnits(node, units, textFragments, elementFragments, parent)); + List lines = buildLines(parent, units, contentX, contentY, availableWidth); + alignLines(parent, lines, contentX, availableWidth); + + for (LineBox line : lines) { + for (InlineFragment fragment : line.fragments()) { + if (fragment.node() instanceof Text text) { + textFragments.computeIfAbsent(text, k -> new ArrayList<>()).add(fragment); + } else if (fragment.node() instanceof Element element) { + elementFragments.computeIfAbsent(element, k -> new ArrayList<>()).add(fragment); + } + } + } + + textFragments.forEach( + (text, fragments) -> { + text.inlineFragments(fragments); + applyUnionBox(text, fragments); + if (!fragments.isEmpty()) { + InlineFragment first = fragments.get(0); + InlineFragment last = fragments.get(fragments.size() - 1); + text.textStartX(first.x() - contentX); + text.textStartY(first.y() - contentY); + text.textEndX(last.x() + last.width() - contentX); + text.textEndY(last.y() - contentY); + } + }); + + for (Node node : nodes) { + if (node instanceof Element element) { + List fragments = new ArrayList<>(); + collectElementFragments(element, textFragments, fragments); + fragments.addAll(elementFragments.getOrDefault(element, List.of())); + applyUnionBox(element, fragments); + } + } + + return lines.stream().map(line -> line.y() + line.height() - contentY).max(Float::compare).orElse(0f); + } + + public boolean inlineNode(Node node) { + if (node instanceof Text) { + return true; + } + return node instanceof Element element && INLINE.equals(element.resolvedStyle().display()); + } + + private void collectUnits( + Node node, + List units, + Map> textFragments, + Map> elementFragments, + Element inheritedParent) { + if (node instanceof Text text) { + text.inlineFragments(List.of()); + text.box().contentPosition(0, 0); + text.box().contentSize(0, 0); + units.addAll(textUnits(text, inheritedParent.resolvedStyle())); + return; + } + + if (!(node instanceof Element element)) { + return; + } + + ResolvedStyle style = element.resolvedStyle(); + setBorders(style, element.box().border()); + setPadding( + inheritedParent.box().content().width(), + inheritedParent.box().content().height(), + style, + element.box().padding()); + setMargins(element, inheritedParent.box().content().width()); + + float left = element.box().margin().left() + element.box().border().left() + element.box().padding().left(); + if (left > 0) { + units.add(new InlineUnit(element, style, null, false, false, true, left)); + } + + for (Node child : element.childNodes()) { + if (inlineNode(child)) { + collectUnits(child, units, textFragments, elementFragments, element); + } + } + + float right = + element.box().padding().right() + element.box().border().right() + element.box().margin().right(); + if (right > 0) { + units.add(new InlineUnit(element, style, null, false, false, true, right)); + } + } + + private List textUnits(Text text, ResolvedStyle style) { + String normalized = normalize(text.content() == null ? "" : text.content(), style); + var result = new ArrayList(); + WhiteSpace whiteSpace = style.whiteSpace(); + boolean preserveSpaces = PRE.equals(whiteSpace) || PRE_WRAP.equals(whiteSpace); + int start = 0; + for (int i = 0; i < normalized.length(); i++) { + char c = normalized.charAt(i); + if (c == '\n') { + addTextUnit(text, style, normalized.substring(start, i), preserveSpaces, result); + result.add(new InlineUnit(text, style, null, false, true, false, 0)); + start = i + 1; + } else if (!preserveSpaces && c == ' ') { + addTextUnit(text, style, normalized.substring(start, i), false, result); + result.add(new InlineUnit(text, style, " ", true, false, false, 0)); + start = i + 1; + } + } + addTextUnit(text, style, normalized.substring(start), preserveSpaces, result); + return result; + } + + private void addTextUnit( + Text text, ResolvedStyle style, String value, boolean preserveSpaces, List units) { + if (value.isEmpty()) { + return; + } + if (preserveSpaces) { + for (int i = 0; i < value.length(); i++) { + units.add(new InlineUnit(text, style, String.valueOf(value.charAt(i)), false, false, false, 0)); + } + } else { + units.add(new InlineUnit(text, style, value, false, false, false, 0)); + } + } + + private String normalize(String text, ResolvedStyle style) { + String normalized = text.replace("\r\n", "\n").replace('\r', '\n'); + int tabSize = Math.max(1, style.tabSize() == null ? 4 : style.tabSize()); + normalized = normalized.replace("\t", " ".repeat(tabSize)); + WhiteSpace whiteSpace = style.whiteSpace(); + if (NORMAL.equals(whiteSpace) || NOWRAP.equals(whiteSpace)) { + return normalized.replaceAll("\\s+", " "); + } + if (PRE_LINE.equals(whiteSpace)) { + return normalized.replaceAll("[ \\f\\t\\x0B]+", " "); + } + return normalized; + } + + private List buildLines( + Element parent, List units, float contentX, float contentY, float availableWidth) { + List lines = new ArrayList<>(); + LineBox line = newLine(contentX, contentY); + float cursorX = contentX; + + for (InlineUnit unit : units) { + if (unit.newline) { + lines.add(closeLine(line)); + contentY += line.height(); + line = newLine(contentX, contentY); + cursorX = contentX; + continue; + } + if (unit.space && line.empty()) { + continue; + } + + float width = unit.width(textMeasurer); + boolean wrap = unit.wraps() && !line.empty() && cursorX + width > contentX + availableWidth; + if (wrap) { + trimTrailingSpace(line); + lines.add(closeLine(line)); + contentY += line.height(); + line = newLine(contentX, contentY); + cursorX = contentX; + if (unit.space) { + continue; + } + } + + if (unit.breakWord(textMeasurer, availableWidth) && cursorX + width > contentX + availableWidth) { + for (InlineUnit part : unit.breakIntoCharacters()) { + if (!line.empty() && cursorX + part.width(textMeasurer) > contentX + availableWidth) { + lines.add(closeLine(line)); + contentY += line.height(); + line = newLine(contentX, contentY); + cursorX = contentX; + } + cursorX = addUnit(line, part, cursorX); + } + } else { + cursorX = addUnit(line, unit, cursorX); + } + } + + trimTrailingSpace(line); + if (!line.empty() || lines.isEmpty()) { + lines.add(closeLine(line)); + } + return lines; + } + + private LineBox newLine(float x, float y) { + var line = new LineBox(); + line.x(x); + line.y(y); + return line; + } + + private float addUnit(LineBox line, InlineUnit unit, float cursorX) { + float width = unit.width(textMeasurer); + float height = unit.lineHeight(textMeasurer); + float baseline = line.y() + unit.metrics(textMeasurer).ascent(); + line.addFragment( + InlineFragment.builder() + .node(unit.node) + .text(unit.text) + .x(cursorX) + .y(line.y()) + .width(width) + .height(height) + .baseline(baseline) + .font(unit.font) + .fontSize(unit.fontSize) + .color(unit.style.color()) + .build()); + return cursorX + width; + } + + private LineBox closeLine(LineBox line) { + if (line.height() == 0) { + line.height(0); + line.baseline(0); + return line; + } + var adjusted = new LineBox(); + adjusted.x(line.x()); + adjusted.y(line.y()); + adjusted.height(line.height()); + adjusted.baseline(line.baseline()); + for (InlineFragment fragment : line.fragments()) { + float dy = line.baseline() - (fragment.baseline() - line.y()); + adjusted.addFragment(fragment.translate(0, dy)); + } + adjusted.width(line.width()); + adjusted.height(line.height()); + adjusted.baseline(line.baseline()); + return adjusted; + } + + private void trimTrailingSpace(LineBox line) { + List fragments = new ArrayList<>(line.fragments()); + if (!fragments.isEmpty()) { + InlineFragment last = fragments.get(fragments.size() - 1); + if (" ".equals(last.text())) { + fragments.remove(fragments.size() - 1); + line.removeLastFragment(); + } + } + } + + private void alignLines(Element parent, List lines, float contentX, float availableWidth) { + for (LineBox line : lines) { + float dx = 0; + if (RIGHT.equals(parent.resolvedStyle().textAlign())) { + dx = availableWidth - line.width(); + } else if (CENTER.equals(parent.resolvedStyle().textAlign())) { + dx = (availableWidth - line.width()) / 2; + } + if (dx != 0) { + float shift = dx; + var fragments = new ArrayList(); + line.fragments().forEach(fragment -> fragments.add(fragment.translate(shift, 0))); + line.x(contentX + dx); + line.replaceFragments(fragments); + } else { + line.x(contentX); + } + } + } + + private void applyUnionBox(Node node, List fragments) { + if (fragments.isEmpty()) { + node.box().contentSize(0, 0); + return; + } + float minX = fragments.stream().map(InlineFragment::x).min(Comparator.naturalOrder()).orElse(0f); + float minY = fragments.stream().map(InlineFragment::y).min(Comparator.naturalOrder()).orElse(0f); + float maxX = + fragments.stream() + .map(fragment -> fragment.x() + fragment.width()) + .max(Comparator.naturalOrder()) + .orElse(minX); + float maxY = + fragments.stream() + .map(fragment -> fragment.y() + fragment.height()) + .max(Comparator.naturalOrder()) + .orElse(minY); + node.box().contentPosition(minX, minY); + node.box().contentSize(maxX - minX, maxY - minY); + } + + private void collectElementFragments( + Element element, Map> textFragments, List fragments) { + for (Node child : element.childNodes()) { + if (child instanceof Text text) { + fragments.addAll(textFragments.getOrDefault(text, List.of())); + } else if (child instanceof Element childElement) { + collectElementFragments(childElement, textFragments, fragments); + } + } + } + + private Font findFont(ResolvedStyle style) { + Set fontFamilies = style.fontFamilies(); + FontStyle fontStyle = style.fontStyle(); + FontWeight fontWeight = style.fontWeight(); + Set fonts = + fontFamilies.stream() + .map(f -> Font.find(f, fontStyle, fontWeight)) + .flatMap(Collection::stream) + .collect(Collectors.toSet()); + return fonts.stream().findFirst().orElse(Font.DEFAULT); + } + + private void setMargins(Element element, float parentWidth) { + ResolvedStyle style = element.resolvedStyle(); + element.box().margin().left(getFloatLengthOptional(style.marginLeft(), parentWidth).orElse(0f)); + element.box().margin().right(getFloatLengthOptional(style.marginRight(), parentWidth).orElse(0f)); + element.box().margin().top(0); + element.box().margin().bottom(0); + } + + private class InlineUnit { + private final Node node; + private final ResolvedStyle style; + private final String text; + private final boolean space; + private final boolean newline; + private final boolean spacer; + private final float spacerWidth; + private final Font font; + private final float fontSize; + + private InlineUnit( + Node node, + ResolvedStyle style, + String text, + boolean space, + boolean newline, + boolean spacer, + float spacerWidth) { + this.node = node; + this.style = style; + this.text = text; + this.space = space; + this.newline = newline; + this.spacer = spacer; + this.spacerWidth = spacerWidth; + this.font = findFont(style); + Float measuredFontSize = StyleUtils.getFontSize(node); + this.fontSize = measuredFontSize == null ? 16f : measuredFontSize; + } + + float width(TextMeasurer measurer) { + if (spacer) { + return spacerWidth; + } + return text == null || text.isEmpty() ? 0 : measurer.measure(text, font, fontSize); + } + + FontMetrics metrics(TextMeasurer measurer) { + return measurer.metrics(font, fontSize, style.lineHeight()); + } + + float lineHeight(TextMeasurer measurer) { + return metrics(measurer).lineHeight(fontSize * style.lineHeight()); + } + + boolean wraps() { + WhiteSpace whiteSpace = style.whiteSpace(); + return !NOWRAP.equals(whiteSpace) && !PRE.equals(whiteSpace); + } + + boolean breakWord(TextMeasurer measurer, float availableWidth) { + OverflowWrap overflowWrap = style.overflowWrap(); + return (BREAK_WORD.equals(overflowWrap) || ANYWHERE.equals(overflowWrap)) + && text != null + && !space + && width(measurer) > availableWidth; + } + + List breakIntoCharacters() { + var parts = new ArrayList(); + for (int i = 0; i < text.length(); i++) { + parts.add(new InlineUnit(node, style, String.valueOf(text.charAt(i)), false, false, false, 0)); + } + return parts; + } + } +} diff --git a/core/src/main/java/com/spinyowl/spinygui/core/layout/impl/LayoutServiceProvider.java b/core/src/main/java/com/spinyowl/spinygui/core/layout/impl/LayoutServiceProvider.java index 7bc06c22..023682ae 100644 --- a/core/src/main/java/com/spinyowl/spinygui/core/layout/impl/LayoutServiceProvider.java +++ b/core/src/main/java/com/spinyowl/spinygui/core/layout/impl/LayoutServiceProvider.java @@ -20,13 +20,15 @@ public static LayoutService create( @NonNull TimeService timeService, @NonNull FontService fontService) { + var textMeasurer = new FontServiceTextMeasurer(fontService); var textLayout = new TextLayoutImpl(fontService); + var inlineFormattingContext = new InlineFormattingContext(textMeasurer); var elementLayoutMap = new HashMap(); LayoutService layoutService = new LayoutServiceImpl(textLayout, elementLayoutMap); elementLayoutMap.put(Display.NONE, new NoneLayout()); - var blockLayout = new BlockLayout(layoutService); + var blockLayout = new BlockLayout(layoutService, inlineFormattingContext); elementLayoutMap.put(Display.BLOCK, blockLayout); var flexLayout = diff --git a/core/src/main/java/com/spinyowl/spinygui/core/node/Text.java b/core/src/main/java/com/spinyowl/spinygui/core/node/Text.java index c19f7ff0..a4b932a5 100644 --- a/core/src/main/java/com/spinyowl/spinygui/core/node/Text.java +++ b/core/src/main/java/com/spinyowl/spinygui/core/node/Text.java @@ -1,5 +1,7 @@ package com.spinyowl.spinygui.core.node; +import com.spinyowl.spinygui.core.layout.InlineFragment; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; @@ -29,6 +31,16 @@ public final class Text extends Node { private float textEndX; private float textEndY; + private final List inlineFragments = new ArrayList<>(); + + public List inlineFragments() { + return Collections.unmodifiableList(inlineFragments); + } + + public void inlineFragments(List fragments) { + inlineFragments.clear(); + inlineFragments.addAll(fragments); + } public Text() { super("#text"); diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/ResolvedStyle.java b/core/src/main/java/com/spinyowl/spinygui/core/style/ResolvedStyle.java index a3a3050a..d6d97308 100644 --- a/core/src/main/java/com/spinyowl/spinygui/core/style/ResolvedStyle.java +++ b/core/src/main/java/com/spinyowl/spinygui/core/style/ResolvedStyle.java @@ -52,6 +52,7 @@ import static com.spinyowl.spinygui.core.style.stylesheet.Properties.MIN_HEIGHT; import static com.spinyowl.spinygui.core.style.stylesheet.Properties.MIN_WIDTH; import static com.spinyowl.spinygui.core.style.stylesheet.Properties.OPACITY; +import static com.spinyowl.spinygui.core.style.stylesheet.Properties.OVERFLOW_WRAP; import static com.spinyowl.spinygui.core.style.stylesheet.Properties.PADDING_BOTTOM; import static com.spinyowl.spinygui.core.style.stylesheet.Properties.PADDING_LEFT; import static com.spinyowl.spinygui.core.style.stylesheet.Properties.PADDING_RIGHT; @@ -60,6 +61,7 @@ import static com.spinyowl.spinygui.core.style.stylesheet.Properties.POSITION; import static com.spinyowl.spinygui.core.style.stylesheet.Properties.RIGHT; import static com.spinyowl.spinygui.core.style.stylesheet.Properties.TAB_SIZE; +import static com.spinyowl.spinygui.core.style.stylesheet.Properties.TEXT_ALIGN; import static com.spinyowl.spinygui.core.style.stylesheet.Properties.TOP; import static com.spinyowl.spinygui.core.style.stylesheet.Properties.WHITE_SPACE; import static com.spinyowl.spinygui.core.style.stylesheet.Properties.WIDTH; @@ -71,8 +73,10 @@ import com.spinyowl.spinygui.core.style.types.BoxShadow; import com.spinyowl.spinygui.core.style.types.Color; import com.spinyowl.spinygui.core.style.types.Display; +import com.spinyowl.spinygui.core.style.types.OverflowWrap; import com.spinyowl.spinygui.core.style.types.PointerEvents; import com.spinyowl.spinygui.core.style.types.Position; +import com.spinyowl.spinygui.core.style.types.TextAlign; import com.spinyowl.spinygui.core.style.types.WhiteSpace; import com.spinyowl.spinygui.core.style.types.background.BackgroundOrigin; import com.spinyowl.spinygui.core.style.types.background.BackgroundRepeat; @@ -698,4 +702,20 @@ public Float lineHeight() { public void lineHeight(Float lineHeight) { set(LINE_HEIGHT, lineHeight); } + + public TextAlign textAlign() { + return get(TEXT_ALIGN); + } + + public void textAlign(TextAlign textAlign) { + set(TEXT_ALIGN, textAlign); + } + + public OverflowWrap overflowWrap() { + return get(OVERFLOW_WRAP); + } + + public void overflowWrap(OverflowWrap overflowWrap) { + set(OVERFLOW_WRAP, overflowWrap); + } } diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/Properties.java b/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/Properties.java index 19eb8f3e..f668e5d0 100644 --- a/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/Properties.java +++ b/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/Properties.java @@ -87,6 +87,7 @@ public final class Properties { public static final String HEIGHT = "height"; public static final String LINE_HEIGHT = "line-height"; + public static final String TEXT_ALIGN = "text-align"; public static final String MIN_WIDTH = "min-width"; public static final String MIN_HEIGHT = "min-height"; @@ -112,4 +113,5 @@ public final class Properties { public static final String OVERFLOW = "overflow"; public static final String OVERFLOW_X = "overflow-x"; public static final String OVERFLOW_Y = "overflow-y"; + public static final String OVERFLOW_WRAP = "overflow-wrap"; } diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/TextPropertyProvider.java b/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/TextPropertyProvider.java new file mode 100644 index 00000000..3f048036 --- /dev/null +++ b/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/TextPropertyProvider.java @@ -0,0 +1,35 @@ +package com.spinyowl.spinygui.core.style.stylesheet.property; + +import static com.spinyowl.spinygui.core.style.stylesheet.Properties.OVERFLOW_WRAP; +import static com.spinyowl.spinygui.core.style.stylesheet.Properties.TEXT_ALIGN; +import static com.spinyowl.spinygui.core.style.stylesheet.Property.checkValue; +import static com.spinyowl.spinygui.core.style.stylesheet.Property.put; + +import com.spinyowl.spinygui.core.style.stylesheet.Property; +import com.spinyowl.spinygui.core.style.stylesheet.PropertyProvider; +import com.spinyowl.spinygui.core.style.stylesheet.term.TermIdent; +import com.spinyowl.spinygui.core.style.types.OverflowWrap; +import com.spinyowl.spinygui.core.style.types.TextAlign; +import java.util.List; + +public class TextPropertyProvider implements PropertyProvider { + + @Override + public List getProperties() { + return List.of( + Property.builder() + .name(TEXT_ALIGN) + .defaultValue(new TermIdent(TextAlign.LEFT.name())) + .inheritable(true) + .updater(put(TEXT_ALIGN, TermIdent.class, TextAlign::find)) + .validator(checkValue(TermIdent.class, TextAlign::contains)) + .build(), + Property.builder() + .name(OVERFLOW_WRAP) + .defaultValue(new TermIdent(OverflowWrap.NORMAL.name())) + .inheritable(true) + .updater(put(OVERFLOW_WRAP, TermIdent.class, OverflowWrap::find)) + .validator(checkValue(TermIdent.class, OverflowWrap::contains)) + .build()); + } +} diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/types/OverflowWrap.java b/core/src/main/java/com/spinyowl/spinygui/core/style/types/OverflowWrap.java new file mode 100644 index 00000000..a9111b81 --- /dev/null +++ b/core/src/main/java/com/spinyowl/spinygui/core/style/types/OverflowWrap.java @@ -0,0 +1,47 @@ +package com.spinyowl.spinygui.core.style.types; + +import static lombok.AccessLevel.PRIVATE; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NonNull; + +/** CSS overflow-wrap. */ +@Getter +@EqualsAndHashCode +@AllArgsConstructor(access = PRIVATE) +public final class OverflowWrap { + + private static final Map VALUES = new HashMap<>(); + + public static final OverflowWrap NORMAL = OverflowWrap.create("normal"); + public static final OverflowWrap BREAK_WORD = OverflowWrap.create("break-word"); + public static final OverflowWrap ANYWHERE = OverflowWrap.create("anywhere"); + + @NonNull private final String name; + + public static OverflowWrap create(@NonNull String name) { + return VALUES.computeIfAbsent(name.toLowerCase(), OverflowWrap::new); + } + + public static OverflowWrap find(@NonNull String name) { + return VALUES.get(name.toLowerCase()); + } + + public static Set values() { + return Set.copyOf(VALUES.values()); + } + + public static boolean contains(@NonNull String name) { + return VALUES.containsKey(name.toLowerCase()); + } + + @Override + public String toString() { + return name; + } +} diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/types/TextAlign.java b/core/src/main/java/com/spinyowl/spinygui/core/style/types/TextAlign.java new file mode 100644 index 00000000..ef159b0b --- /dev/null +++ b/core/src/main/java/com/spinyowl/spinygui/core/style/types/TextAlign.java @@ -0,0 +1,48 @@ +package com.spinyowl.spinygui.core.style.types; + +import static lombok.AccessLevel.PRIVATE; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NonNull; + +/** CSS text-align. */ +@Getter +@EqualsAndHashCode +@AllArgsConstructor(access = PRIVATE) +public final class TextAlign { + + private static final Map VALUES = new HashMap<>(); + + public static final TextAlign LEFT = TextAlign.create("left"); + public static final TextAlign RIGHT = TextAlign.create("right"); + public static final TextAlign CENTER = TextAlign.create("center"); + public static final TextAlign JUSTIFY = TextAlign.create("justify"); + + @NonNull private final String name; + + public static TextAlign create(@NonNull String name) { + return VALUES.computeIfAbsent(name.toLowerCase(), TextAlign::new); + } + + public static TextAlign find(@NonNull String name) { + return VALUES.get(name.toLowerCase()); + } + + public static Set values() { + return Set.copyOf(VALUES.values()); + } + + public static boolean contains(@NonNull String name) { + return VALUES.containsKey(name.toLowerCase()); + } + + @Override + public String toString() { + return name; + } +} diff --git a/core/src/main/java/com/spinyowl/spinygui/core/system/font/FontService.java b/core/src/main/java/com/spinyowl/spinygui/core/system/font/FontService.java index d7045b32..a2488204 100644 --- a/core/src/main/java/com/spinyowl/spinygui/core/system/font/FontService.java +++ b/core/src/main/java/com/spinyowl/spinygui/core/system/font/FontService.java @@ -46,6 +46,17 @@ TextMetrics getTextMetrics( float maxWidth, boolean wordWrap); + /** + * Calculates font vertical metrics. + * + * @param font font to use. + * @param fontSize font size. + * @param lineHeight requested CSS line-height multiplier. + * @return font metrics in pixels. + */ + com.spinyowl.spinygui.core.layout.FontMetrics getFontMetrics( + @NonNull Font font, float fontSize, float lineHeight); + /** * Calculates text line metrics. * diff --git a/core/src/main/java/com/spinyowl/spinygui/core/system/font/impl/FontServiceImpl.java b/core/src/main/java/com/spinyowl/spinygui/core/system/font/impl/FontServiceImpl.java index 37bc6bd0..fca349bd 100644 --- a/core/src/main/java/com/spinyowl/spinygui/core/system/font/impl/FontServiceImpl.java +++ b/core/src/main/java/com/spinyowl/spinygui/core/system/font/impl/FontServiceImpl.java @@ -4,15 +4,16 @@ import static org.lwjgl.stb.STBTruetype.STBTT_MS_LANG_ENGLISH; import static org.lwjgl.stb.STBTruetype.STBTT_PLATFORM_ID_MICROSOFT; import static org.lwjgl.stb.STBTruetype.stbtt_GetCodepointHMetrics; +import static org.lwjgl.stb.STBTruetype.stbtt_GetFontVMetrics; import static org.lwjgl.stb.STBTruetype.stbtt_GetFontNameString; import static org.lwjgl.stb.STBTruetype.stbtt_ScaleForMappingEmToPixels; -import static org.lwjgl.stb.STBTruetype.stbtt_ScaleForPixelHeight; import static org.slf4j.LoggerFactory.getLogger; import com.spinyowl.spinygui.core.font.Font; import com.spinyowl.spinygui.core.font.FontStretch; import com.spinyowl.spinygui.core.font.FontStyle; import com.spinyowl.spinygui.core.font.FontWeight; +import com.spinyowl.spinygui.core.layout.FontMetrics; import com.spinyowl.spinygui.core.system.font.FontLoadingException; import com.spinyowl.spinygui.core.system.font.FontService; import com.spinyowl.spinygui.core.system.font.FontStorage; @@ -197,6 +198,33 @@ public TextMetrics getTextMetrics( } } + @Override + public FontMetrics getFontMetrics(@NonNull Font font, float fontSize, float lineHeight) { + STBTTFontinfo fontInfo = getFontInfo(font.path()); + try (MemoryStack stack = MemoryStack.stackPush()) { + IntBuffer ascent = stack.mallocInt(1); + IntBuffer descent = stack.mallocInt(1); + IntBuffer lineGap = stack.mallocInt(1); + stbtt_GetFontVMetrics(fontInfo, ascent, descent, lineGap); + + float scaleFactor = stbtt_ScaleForMappingEmToPixels(fontInfo, fontSize); + float requestedLineHeight = fontSize * lineHeight; + float metricsAscent = ascent.get(0) * scaleFactor; + float metricsDescent = Math.abs(descent.get(0) * scaleFactor); + float metricsLineGap = Math.max(0, lineGap.get(0) * scaleFactor); + float naturalLineHeight = metricsAscent + metricsDescent + metricsLineGap; + if (requestedLineHeight > naturalLineHeight) { + metricsLineGap += requestedLineHeight - naturalLineHeight; + } + if (roundToPixel) { + metricsAscent = Math.round(metricsAscent); + metricsDescent = Math.round(metricsDescent); + metricsLineGap = Math.round(metricsLineGap); + } + return new FontMetrics(metricsAscent, metricsDescent, metricsLineGap); + } + } + @Override public TextLineMetrics getTextLineMetrics( @NonNull String text, @NonNull Font font, float fontSize, float lineHeight) { @@ -206,7 +234,7 @@ public TextLineMetrics getTextLineMetrics( IntBuffer pAdvance = stack.mallocInt(1); IntBuffer pLeftSideBearing = stack.mallocInt(1); - float scaleFactor = stbtt_ScaleForPixelHeight(fontInfo, fontSize); + float scaleFactor = stbtt_ScaleForMappingEmToPixels(fontInfo, fontSize); int textLength = text.length(); diff --git a/core/src/test/java/com/spinyowl/spinygui/core/layout/impl/BlockLayoutTest.java b/core/src/test/java/com/spinyowl/spinygui/core/layout/impl/BlockLayoutTest.java new file mode 100644 index 00000000..5d7f0097 --- /dev/null +++ b/core/src/test/java/com/spinyowl/spinygui/core/layout/impl/BlockLayoutTest.java @@ -0,0 +1,139 @@ +package com.spinyowl.spinygui.core.layout.impl; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.spinyowl.spinygui.core.font.Font; +import com.spinyowl.spinygui.core.font.FontStyle; +import com.spinyowl.spinygui.core.font.FontWeight; +import com.spinyowl.spinygui.core.layout.ElementLayout; +import com.spinyowl.spinygui.core.layout.FontMetrics; +import com.spinyowl.spinygui.core.layout.LayoutContext; +import com.spinyowl.spinygui.core.layout.LayoutService; +import com.spinyowl.spinygui.core.layout.TextMeasurer; +import com.spinyowl.spinygui.core.node.Element; +import com.spinyowl.spinygui.core.node.Frame; +import com.spinyowl.spinygui.core.node.Node; +import com.spinyowl.spinygui.core.node.NodeBuilder; +import com.spinyowl.spinygui.core.node.Text; +import com.spinyowl.spinygui.core.style.ResolvedStyle; +import com.spinyowl.spinygui.core.style.types.Color; +import com.spinyowl.spinygui.core.style.types.Display; +import com.spinyowl.spinygui.core.style.types.OverflowWrap; +import com.spinyowl.spinygui.core.style.types.Position; +import com.spinyowl.spinygui.core.style.types.TextAlign; +import com.spinyowl.spinygui.core.style.types.WhiteSpace; +import com.spinyowl.spinygui.core.style.types.border.BorderStyle; +import com.spinyowl.spinygui.core.style.types.length.Length; +import com.spinyowl.spinygui.core.style.types.length.Unit; +import java.util.Set; +import lombok.NonNull; +import org.junit.jupiter.api.Test; + +class BlockLayoutTest { + + @Test + void layout_whenNestedBlockHasInlineTextBeforeBlockChild_doesNotDoubleCountParentOffset() { + Frame frame = NodeBuilder.frame(); + frame.frameSize(200, 200); + style(frame, 0); + + Element wrapper = NodeBuilder.div(); + style(wrapper, 8); + Element mixed = NodeBuilder.div(); + style(mixed, 8); + Text text = NodeBuilder.text("c1"); + Element blockChild = NodeBuilder.div("c11"); + style(blockChild, 8); + + mixed.addChildren(text, blockChild); + wrapper.addChild(mixed); + frame.addChild(wrapper); + + RecursiveLayoutService layoutService = new RecursiveLayoutService(); + BlockLayout blockLayout = + new BlockLayout(layoutService, new InlineFormattingContext(new FixedTextMeasurer())); + layoutService.blockLayout(blockLayout); + + blockLayout.layout(frame, new LayoutContext()); + + assertEquals(8, text.inlineFragments().get(0).y()); + assertEquals(18, blockChild.box().borderBox().y()); + } + + private void style(Element element, float borderWidth) { + ResolvedStyle style = element.resolvedStyle(); + style.display(Display.BLOCK); + style.position(Position.STATIC); + style.width(Unit.AUTO); + style.height(Unit.AUTO); + style.minWidth(null); + style.maxWidth(null); + style.minHeight(null); + style.maxHeight(null); + style.paddingTop(Length.ZERO); + style.paddingRight(Length.ZERO); + style.paddingBottom(Length.ZERO); + style.paddingLeft(Length.ZERO); + style.marginTop(Length.ZERO); + style.marginRight(Length.ZERO); + style.marginBottom(Length.ZERO); + style.marginLeft(Length.ZERO); + style.borderTopWidth(Length.pixel(borderWidth)); + style.borderRightWidth(Length.pixel(borderWidth)); + style.borderBottomWidth(Length.pixel(borderWidth)); + style.borderLeftWidth(Length.pixel(borderWidth)); + BorderStyle borderStyle = borderWidth == 0 ? BorderStyle.NONE : BorderStyle.SOLID; + style.borderTopStyle(borderStyle); + style.borderRightStyle(borderStyle); + style.borderBottomStyle(borderStyle); + style.borderLeftStyle(borderStyle); + style.fontFamilies(Set.of("Roboto")); + style.fontStyle(FontStyle.NORMAL); + style.fontWeight(FontWeight.NORMAL); + style.fontSize(Length.pixel(10)); + style.lineHeight(1f); + style.color(Color.BLACK); + style.whiteSpace(WhiteSpace.NORMAL); + style.textAlign(TextAlign.LEFT); + style.overflowWrap(OverflowWrap.NORMAL); + style.tabSize(4); + } + + private static class RecursiveLayoutService implements LayoutService { + private ElementLayout blockLayout; + + void blockLayout(ElementLayout blockLayout) { + this.blockLayout = blockLayout; + } + + @Override + public void layout(@NonNull Frame frame) { + layoutNode(frame, new LayoutContext()); + } + + @Override + public void layoutNode(@NonNull Node node, @NonNull LayoutContext context) { + if (node instanceof Element element) { + blockLayout.layout(element, context); + } + } + + @Override + public void layoutChildNodes(@NonNull Element element, @NonNull LayoutContext context) { + LayoutContext inner = new LayoutContext(); + element.childNodes().forEach(node -> layoutNode(node, inner)); + } + } + + private static class FixedTextMeasurer implements TextMeasurer { + @Override + public float measure(@NonNull String text, @NonNull Font font, float fontSize) { + return text.length() * 10f; + } + + @Override + public FontMetrics metrics(@NonNull Font font, float fontSize, float lineHeight) { + return new FontMetrics(8, 2, Math.max(0, fontSize * lineHeight - 10)); + } + } +} diff --git a/core/src/test/java/com/spinyowl/spinygui/core/layout/impl/InlineFormattingContextTest.java b/core/src/test/java/com/spinyowl/spinygui/core/layout/impl/InlineFormattingContextTest.java new file mode 100644 index 00000000..5cf50eb9 --- /dev/null +++ b/core/src/test/java/com/spinyowl/spinygui/core/layout/impl/InlineFormattingContextTest.java @@ -0,0 +1,226 @@ +package com.spinyowl.spinygui.core.layout.impl; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.spinyowl.spinygui.core.font.Font; +import com.spinyowl.spinygui.core.font.FontStyle; +import com.spinyowl.spinygui.core.font.FontWeight; +import com.spinyowl.spinygui.core.layout.FontMetrics; +import com.spinyowl.spinygui.core.layout.TextMeasurer; +import com.spinyowl.spinygui.core.node.Element; +import com.spinyowl.spinygui.core.node.NodeBuilder; +import com.spinyowl.spinygui.core.node.Text; +import com.spinyowl.spinygui.core.style.ResolvedStyle; +import com.spinyowl.spinygui.core.style.types.Color; +import com.spinyowl.spinygui.core.style.types.Display; +import com.spinyowl.spinygui.core.style.types.OverflowWrap; +import com.spinyowl.spinygui.core.style.types.Position; +import com.spinyowl.spinygui.core.style.types.TextAlign; +import com.spinyowl.spinygui.core.style.types.WhiteSpace; +import com.spinyowl.spinygui.core.style.types.length.Length; +import java.util.List; +import java.util.Set; +import lombok.NonNull; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class InlineFormattingContextTest { + private InlineFormattingContext formattingContext; + private Element parent; + + @BeforeEach + void setUp() { + formattingContext = new InlineFormattingContext(new FixedTextMeasurer()); + parent = NodeBuilder.div(); + style(parent, Display.BLOCK); + parent.box().contentSize(50, 0); + } + + @Test + void layout_whenTextFits_placesItOnSingleLine() { + Text text = NodeBuilder.text("abc"); + parent.addChild(text); + + float height = formattingContext.layout(parent, List.of(text), 0); + + assertEquals(10, height); + assertEquals(1, text.inlineFragments().size()); + assertEquals("abc", text.inlineFragments().get(0).text()); + assertEquals(0, text.inlineFragments().get(0).x()); + assertEquals(30, text.box().content().width()); + } + + @Test + void layout_whenTextWrapsAtSpace_createsMultipleFragments() { + Text text = NodeBuilder.text("hello world"); + parent.addChild(text); + + float height = formattingContext.layout(parent, List.of(text), 0); + + assertEquals(20, height); + assertEquals(2, text.inlineFragments().size()); + assertEquals("hello", text.inlineFragments().get(0).text()); + assertEquals("world", text.inlineFragments().get(1).text()); + assertEquals(10, text.inlineFragments().get(1).y()); + } + + @Test + void layout_whenOverflowWrapBreakWord_breaksLongWord() { + parent.resolvedStyle().overflowWrap(OverflowWrap.BREAK_WORD); + Text text = NodeBuilder.text("abcdef"); + parent.addChild(text); + + float height = formattingContext.layout(parent, List.of(text), 0); + + assertEquals(20, height); + assertEquals(6, text.inlineFragments().size()); + assertEquals("a", text.inlineFragments().get(0).text()); + assertEquals("f", text.inlineFragments().get(5).text()); + assertEquals(10, text.inlineFragments().get(5).y()); + } + + @Test + void layout_whenConsecutiveTextNodes_shareLineBox() { + parent.box().content().width(200); + Text first = NodeBuilder.text("Hello"); + Text second = NodeBuilder.text(" world"); + parent.addChildren(first, second); + + formattingContext.layout(parent, List.of(first, second), 0); + + assertEquals(0, first.inlineFragments().get(0).y()); + assertEquals(0, second.inlineFragments().get(0).y()); + assertEquals(60, second.inlineFragments().get(0).x()); + } + + @Test + void layout_whenInlineElementBetweenText_participatesInLine() { + parent.box().content().width(200); + Text left = NodeBuilder.text("a"); + Element inline = new Element("span"); + style(inline, Display.INLINE); + Text middle = NodeBuilder.text("b"); + inline.addChild(middle); + Text right = NodeBuilder.text("c"); + parent.addChildren(left, inline, right); + + formattingContext.layout(parent, List.of(left, inline, right), 0); + + assertEquals(10, middle.inlineFragments().get(0).x()); + assertEquals(20, right.inlineFragments().get(0).x()); + assertEquals(10, inline.box().content().width()); + } + + @Test + void layout_whenPreLine_preservesNewlines() { + parent.resolvedStyle().whiteSpace(WhiteSpace.PRE_LINE); + Text text = NodeBuilder.text("a\nb"); + parent.addChild(text); + + float height = formattingContext.layout(parent, List.of(text), 0); + + assertEquals(20, height); + assertEquals(2, text.inlineFragments().size()); + assertEquals(10, text.inlineFragments().get(1).y()); + } + + @Test + void layout_whenPreWithTab_expandsTabUsingTabSize() { + parent.resolvedStyle().whiteSpace(WhiteSpace.PRE); + parent.resolvedStyle().tabSize(4); + parent.box().content().width(200); + Text text = NodeBuilder.text("a\tb"); + parent.addChild(text); + + formattingContext.layout(parent, List.of(text), 0); + + assertEquals(6, text.inlineFragments().size()); + assertEquals("b", text.inlineFragments().get(5).text()); + assertEquals(50, text.inlineFragments().get(5).x()); + } + + @Test + void layout_whenTextAlignCenter_offsetsLineFragments() { + parent.box().content().width(100); + parent.resolvedStyle().textAlign(TextAlign.CENTER); + Text text = NodeBuilder.text("abc"); + parent.addChild(text); + + formattingContext.layout(parent, List.of(text), 0); + + assertEquals(35, text.inlineFragments().get(0).x()); + } + + @Test + void layout_whenTextBecomesEmpty_clearsPreviousBox() { + Text text = NodeBuilder.text("abc"); + parent.addChild(text); + formattingContext.layout(parent, List.of(text), 0); + assertEquals(30, text.box().content().width()); + + text.content(" "); + formattingContext.layout(parent, List.of(text), 0); + + assertEquals(0, text.inlineFragments().size()); + assertEquals(0, text.box().content().width()); + assertEquals(0, text.box().content().height()); + } + + @Test + void layout_whenInlineElementHasPercentPadding_usesParentContentAsBase() { + parent.box().content().width(100); + Element inline = new Element("span"); + style(inline, Display.INLINE); + inline.resolvedStyle().paddingLeft(Length.percent(0.1f)); + inline.resolvedStyle().paddingRight(Length.percent(0.1f)); + Text text = NodeBuilder.text("a"); + inline.addChild(text); + parent.addChild(inline); + + formattingContext.layout(parent, List.of(inline), 0); + + assertEquals(10, text.inlineFragments().get(0).x()); + assertEquals(30, inline.box().content().width()); + } + + @Test + void layout_whenTextAlignRight_appliesExpectedOffset() { + parent.box().content().width(100); + parent.resolvedStyle().textAlign(TextAlign.RIGHT); + Text text = NodeBuilder.text("abc"); + parent.addChild(text); + + formattingContext.layout(parent, List.of(text), 0); + + assertEquals(70, text.inlineFragments().get(0).x()); + assertEquals(30, text.box().content().width()); + } + + private void style(Element element, Display display) { + ResolvedStyle style = element.resolvedStyle(); + style.display(display); + style.position(Position.STATIC); + style.fontFamilies(Set.of("Roboto")); + style.fontStyle(FontStyle.NORMAL); + style.fontWeight(FontWeight.NORMAL); + style.fontSize(Length.pixel(10)); + style.lineHeight(1f); + style.color(Color.BLACK); + style.whiteSpace(WhiteSpace.NORMAL); + style.textAlign(TextAlign.LEFT); + style.overflowWrap(OverflowWrap.NORMAL); + style.tabSize(4); + } + + private static class FixedTextMeasurer implements TextMeasurer { + @Override + public float measure(@NonNull String text, @NonNull Font font, float fontSize) { + return text.length() * 10f; + } + + @Override + public FontMetrics metrics(@NonNull Font font, float fontSize, float lineHeight) { + return new FontMetrics(8, 2, Math.max(0, fontSize * lineHeight - 10)); + } + } +} From 369485dd497df8d4c1bdf43a8447dd9eb8eeacd7 Mon Sep 17 00:00:00 2001 From: SpinyOwl Date: Tue, 12 May 2026 02:52:31 +0200 Subject: [PATCH 004/121] Unify font text metrics --- .../spinygui/core/layout/FontMetrics.java | 14 -- .../spinygui/core/layout/TextMeasurer.java | 6 +- .../layout/impl/FontServiceTextMeasurer.java | 12 +- .../layout/impl/InlineFormattingContext.java | 20 +- .../core/layout/impl/TextLayoutImpl.java | 8 +- .../core/system/font/FontMetrics.java | 12 + .../core/system/font/FontService.java | 37 ++- .../core/system/font/TextLineMetrics.java | 12 + .../core/system/font/TextMetrics.java | 8 +- .../system/font/impl/FontServiceImpl.java | 215 +++++++++++------- .../core/layout/impl/BlockLayoutTest.java | 23 +- .../impl/InlineFormattingContextTest.java | 39 +++- .../system/font/impl/FontServiceImplTest.java | 65 ++++++ .../font-service-unified-text-metrics.md | 47 ++++ 14 files changed, 375 insertions(+), 143 deletions(-) delete mode 100644 core/src/main/java/com/spinyowl/spinygui/core/layout/FontMetrics.java create mode 100644 core/src/main/java/com/spinyowl/spinygui/core/system/font/FontMetrics.java create mode 100644 core/src/test/java/com/spinyowl/spinygui/core/system/font/impl/FontServiceImplTest.java create mode 100644 docs/features/font-service-unified-text-metrics.md diff --git a/core/src/main/java/com/spinyowl/spinygui/core/layout/FontMetrics.java b/core/src/main/java/com/spinyowl/spinygui/core/layout/FontMetrics.java deleted file mode 100644 index 62fdf3c3..00000000 --- a/core/src/main/java/com/spinyowl/spinygui/core/layout/FontMetrics.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.spinyowl.spinygui.core.layout; - -import lombok.Value; - -@Value -public class FontMetrics { - float ascent; - float descent; - float lineGap; - - public float lineHeight(float requestedLineHeight) { - return Math.max(requestedLineHeight, ascent + descent + lineGap); - } -} diff --git a/core/src/main/java/com/spinyowl/spinygui/core/layout/TextMeasurer.java b/core/src/main/java/com/spinyowl/spinygui/core/layout/TextMeasurer.java index 9502afdd..f97fd943 100644 --- a/core/src/main/java/com/spinyowl/spinygui/core/layout/TextMeasurer.java +++ b/core/src/main/java/com/spinyowl/spinygui/core/layout/TextMeasurer.java @@ -1,11 +1,11 @@ package com.spinyowl.spinygui.core.layout; import com.spinyowl.spinygui.core.font.Font; +import com.spinyowl.spinygui.core.system.font.TextLineMetrics; import lombok.NonNull; public interface TextMeasurer { - float measure(@NonNull String text, @NonNull Font font, float fontSize); - - FontMetrics metrics(@NonNull Font font, float fontSize, float lineHeight); + TextLineMetrics measure( + @NonNull String text, @NonNull Font font, float fontSize, float lineHeight); } diff --git a/core/src/main/java/com/spinyowl/spinygui/core/layout/impl/FontServiceTextMeasurer.java b/core/src/main/java/com/spinyowl/spinygui/core/layout/impl/FontServiceTextMeasurer.java index 977125a7..1e419a92 100644 --- a/core/src/main/java/com/spinyowl/spinygui/core/layout/impl/FontServiceTextMeasurer.java +++ b/core/src/main/java/com/spinyowl/spinygui/core/layout/impl/FontServiceTextMeasurer.java @@ -1,9 +1,9 @@ package com.spinyowl.spinygui.core.layout.impl; import com.spinyowl.spinygui.core.font.Font; -import com.spinyowl.spinygui.core.layout.FontMetrics; import com.spinyowl.spinygui.core.layout.TextMeasurer; import com.spinyowl.spinygui.core.system.font.FontService; +import com.spinyowl.spinygui.core.system.font.TextLineMetrics; import lombok.NonNull; import lombok.RequiredArgsConstructor; @@ -12,12 +12,8 @@ public class FontServiceTextMeasurer implements TextMeasurer { @NonNull private final FontService fontService; @Override - public float measure(@NonNull String text, @NonNull Font font, float fontSize) { - return fontService.getTextLineMetrics(text, font, fontSize, 1).width(); - } - - @Override - public FontMetrics metrics(@NonNull Font font, float fontSize, float lineHeight) { - return fontService.getFontMetrics(font, fontSize, lineHeight); + public TextLineMetrics measure( + @NonNull String text, @NonNull Font font, float fontSize, float lineHeight) { + return fontService.measureText(text, font, fontSize, lineHeight).lines().get(0); } } diff --git a/core/src/main/java/com/spinyowl/spinygui/core/layout/impl/InlineFormattingContext.java b/core/src/main/java/com/spinyowl/spinygui/core/layout/impl/InlineFormattingContext.java index 9053ec20..f8ec4089 100644 --- a/core/src/main/java/com/spinyowl/spinygui/core/layout/impl/InlineFormattingContext.java +++ b/core/src/main/java/com/spinyowl/spinygui/core/layout/impl/InlineFormattingContext.java @@ -17,7 +17,6 @@ import com.spinyowl.spinygui.core.font.Font; import com.spinyowl.spinygui.core.font.FontStyle; import com.spinyowl.spinygui.core.font.FontWeight; -import com.spinyowl.spinygui.core.layout.FontMetrics; import com.spinyowl.spinygui.core.layout.InlineFragment; import com.spinyowl.spinygui.core.layout.LineBox; import com.spinyowl.spinygui.core.layout.TextMeasurer; @@ -28,6 +27,7 @@ import com.spinyowl.spinygui.core.style.stylesheet.util.StyleUtils; import com.spinyowl.spinygui.core.style.types.OverflowWrap; import com.spinyowl.spinygui.core.style.types.WhiteSpace; +import com.spinyowl.spinygui.core.system.font.TextLineMetrics; import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; @@ -260,7 +260,7 @@ private LineBox newLine(float x, float y) { private float addUnit(LineBox line, InlineUnit unit, float cursorX) { float width = unit.width(textMeasurer); float height = unit.lineHeight(textMeasurer); - float baseline = line.y() + unit.metrics(textMeasurer).ascent(); + float baseline = line.y() + unit.baseline(textMeasurer); line.addFragment( InlineFragment.builder() .node(unit.node) @@ -391,6 +391,7 @@ private class InlineUnit { private final float spacerWidth; private final Font font; private final float fontSize; + private TextLineMetrics measurement; private InlineUnit( Node node, @@ -416,15 +417,22 @@ float width(TextMeasurer measurer) { if (spacer) { return spacerWidth; } - return text == null || text.isEmpty() ? 0 : measurer.measure(text, font, fontSize); + return text == null || text.isEmpty() ? 0 : measurement(measurer).width(); } - FontMetrics metrics(TextMeasurer measurer) { - return measurer.metrics(font, fontSize, style.lineHeight()); + TextLineMetrics measurement(TextMeasurer measurer) { + if (measurement == null) { + measurement = measurer.measure(text == null ? "" : text, font, fontSize, style.lineHeight()); + } + return measurement; } float lineHeight(TextMeasurer measurer) { - return metrics(measurer).lineHeight(fontSize * style.lineHeight()); + return measurement(measurer).height(); + } + + float baseline(TextMeasurer measurer) { + return measurement(measurer).baseline(); } boolean wraps() { diff --git a/core/src/main/java/com/spinyowl/spinygui/core/layout/impl/TextLayoutImpl.java b/core/src/main/java/com/spinyowl/spinygui/core/layout/impl/TextLayoutImpl.java index e7202794..bdc0a5dc 100644 --- a/core/src/main/java/com/spinyowl/spinygui/core/layout/impl/TextLayoutImpl.java +++ b/core/src/main/java/com/spinyowl/spinygui/core/layout/impl/TextLayoutImpl.java @@ -66,7 +66,7 @@ public void layout(Text text, LayoutContext context) { } TextMetrics metrics = - fontService.getTextMetrics( + fontService.measureText( text.content(), startX, fontToUse, fontSize, lineHeight, parentWidth, false); text.textStartX(startX); @@ -74,11 +74,11 @@ public void layout(Text text, LayoutContext context) { TextLineMetrics lastLine = Iterables.getLast(metrics.lines()); float lastTextEndX = lastLine.width(); - float fullLineHeight = metrics.fullLineHeight(); - float lastTextEndY = metrics.height() - fullLineHeight; // top border of last text line. + float measuredLineHeight = metrics.lineHeight(); + float lastTextEndY = metrics.height() - measuredLineHeight; // top border of last text line. if (lastTextEndX >= parentWidth) { lastTextEndX = 0; - lastTextEndY += fullLineHeight; + lastTextEndY += measuredLineHeight; } text.textEndX(lastTextEndX); diff --git a/core/src/main/java/com/spinyowl/spinygui/core/system/font/FontMetrics.java b/core/src/main/java/com/spinyowl/spinygui/core/system/font/FontMetrics.java new file mode 100644 index 00000000..2e23df63 --- /dev/null +++ b/core/src/main/java/com/spinyowl/spinygui/core/system/font/FontMetrics.java @@ -0,0 +1,12 @@ +package com.spinyowl.spinygui.core.system.font; + +import lombok.Value; + +@Value +public class FontMetrics { + float ascent; + float descent; + float lineGap; + float lineHeight; + float baseline; +} diff --git a/core/src/main/java/com/spinyowl/spinygui/core/system/font/FontService.java b/core/src/main/java/com/spinyowl/spinygui/core/system/font/FontService.java index a2488204..d5c05865 100644 --- a/core/src/main/java/com/spinyowl/spinygui/core/system/font/FontService.java +++ b/core/src/main/java/com/spinyowl/spinygui/core/system/font/FontService.java @@ -23,6 +23,40 @@ public interface FontService { */ boolean isFontAvailable(@NonNull Font font); + /** + * Measures a single text line and returns horizontal and vertical font metrics in one result. + * + * @param text text to calculate metrics for. + * @param font font to use. + * @param fontSize font size. + * @param lineHeight requested CSS line-height multiplier. + * @return text metrics. + */ + TextMetrics measureText( + @NonNull String text, @NonNull Font font, float fontSize, float lineHeight); + + /** + * Measures text with wrapping and returns horizontal and vertical font metrics in one result. + * + * @param text text to calculate metrics for. + * @param offsetX starting x offset for the first line of text. + * @param font font to use. + * @param fontSize font size. + * @param lineHeight requested CSS line-height multiplier. + * @param maxWidth maximum width of text in pixels. + * @param wordWrap if true, text will be wrapped by nearest characters to maxWidth, otherwise text + * will be wrapped by spaces to fit maxWidth. + * @return text metrics. + */ + TextMetrics measureText( + @NonNull String text, + float offsetX, + @NonNull Font font, + float fontSize, + float lineHeight, + float maxWidth, + boolean wordWrap); + /** * Calculates text metrics. * @@ -54,8 +88,7 @@ TextMetrics getTextMetrics( * @param lineHeight requested CSS line-height multiplier. * @return font metrics in pixels. */ - com.spinyowl.spinygui.core.layout.FontMetrics getFontMetrics( - @NonNull Font font, float fontSize, float lineHeight); + FontMetrics getFontMetrics(@NonNull Font font, float fontSize, float lineHeight); /** * Calculates text line metrics. diff --git a/core/src/main/java/com/spinyowl/spinygui/core/system/font/TextLineMetrics.java b/core/src/main/java/com/spinyowl/spinygui/core/system/font/TextLineMetrics.java index 297ee115..c7475365 100644 --- a/core/src/main/java/com/spinyowl/spinygui/core/system/font/TextLineMetrics.java +++ b/core/src/main/java/com/spinyowl/spinygui/core/system/font/TextLineMetrics.java @@ -16,6 +16,12 @@ public final class TextLineMetrics { private CharSequence characters; + /** Start index in the original measured text. */ + private int startIndex; + + /** End index in the original measured text, exclusive. */ + private int endIndex; + /** Character count in the line. */ private int charCount; @@ -25,6 +31,12 @@ public final class TextLineMetrics { /** Height of the line in pixels. */ private float height; + /** Baseline offset from the top of the line in pixels. */ + private float baseline; + + /** Font metrics used to measure this line. */ + private FontMetrics fontMetrics; + public String toString() { return characters.toString(); } diff --git a/core/src/main/java/com/spinyowl/spinygui/core/system/font/TextMetrics.java b/core/src/main/java/com/spinyowl/spinygui/core/system/font/TextMetrics.java index fc7bacc5..41354bb1 100644 --- a/core/src/main/java/com/spinyowl/spinygui/core/system/font/TextMetrics.java +++ b/core/src/main/java/com/spinyowl/spinygui/core/system/font/TextMetrics.java @@ -1,6 +1,6 @@ package com.spinyowl.spinygui.core.system.font; -import com.google.common.collect.ImmutableSet; +import java.util.List; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Builder; @@ -18,8 +18,10 @@ @Builder public final class TextMetrics { - @Singular private final ImmutableSet lines; + @Singular private final List lines; + private float width; private float height; - private float fullLineHeight; + private float lineHeight; + private FontMetrics fontMetrics; } diff --git a/core/src/main/java/com/spinyowl/spinygui/core/system/font/impl/FontServiceImpl.java b/core/src/main/java/com/spinyowl/spinygui/core/system/font/impl/FontServiceImpl.java index fca349bd..d0a10d1e 100644 --- a/core/src/main/java/com/spinyowl/spinygui/core/system/font/impl/FontServiceImpl.java +++ b/core/src/main/java/com/spinyowl/spinygui/core/system/font/impl/FontServiceImpl.java @@ -13,7 +13,7 @@ import com.spinyowl.spinygui.core.font.FontStretch; import com.spinyowl.spinygui.core.font.FontStyle; import com.spinyowl.spinygui.core.font.FontWeight; -import com.spinyowl.spinygui.core.layout.FontMetrics; +import com.spinyowl.spinygui.core.system.font.FontMetrics; import com.spinyowl.spinygui.core.system.font.FontLoadingException; import com.spinyowl.spinygui.core.system.font.FontService; import com.spinyowl.spinygui.core.system.font.FontStorage; @@ -22,7 +22,9 @@ import java.nio.ByteBuffer; import java.nio.IntBuffer; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import lombok.NonNull; @@ -113,7 +115,14 @@ private String getFontFamily(STBTTFontinfo fontInfo) { : typographicFontFamily; } - public TextMetrics getTextMetrics( + @Override + public TextMetrics measureText( + @NonNull String text, @NonNull Font font, float fontSize, float lineHeight) { + return measureText(text, 0, font, fontSize, lineHeight, Float.MAX_VALUE, false); + } + + @Override + public TextMetrics measureText( @NonNull String text, float offsetX, @NonNull Font font, @@ -122,7 +131,7 @@ public TextMetrics getTextMetrics( float maxWidth, boolean wordWrap) { if (maxWidth < 0.1) { - TextMetrics.builder().height(0).fullLineHeight(0).build(); + return emptyTextMetrics(font, fontSize, lineHeight); } STBTTFontinfo fontInfo = getFontInfo(font.path()); @@ -132,75 +141,115 @@ public TextMetrics getTextMetrics( IntBuffer pLeftSideBearing = stack.mallocInt(1); float scaleFactor = stbtt_ScaleForMappingEmToPixels(fontInfo, fontSize); - - int textLength = text.length(); - float lineWidth = offsetX; - float fullLineHeight = lineHeight * fontSize; - if (roundToPixel) { - fullLineHeight = Math.round(fullLineHeight); - } - - TextLineMetrics.TextLineMetricsBuilder textLineMetrics = - TextLineMetrics.builder().height(fullLineHeight); - + FontMetrics fontMetrics = measureFontMetrics(fontInfo, fontSize, lineHeight); + int length = text.length(); + List lines = new ArrayList<>(); + float currentWidth = offsetX; + float maxLineWidth = 0; + int lineCount = 0; + int lineStart = 0; int lastSpace = -1; + int lastSpaceEnd = -1; float lastSpaceWidth = 0; - - int lineStart = 0; - int lineEnd = 0; - int i = 0; - int lineCount = 1; - TextMetrics.TextMetricsBuilder textMetrics = TextMetrics.builder(); - while (i < textLength) { - int newLineStart = i + 1; - // get codepoint - int codePointSize = getCodePointSize(text, textLength, i, pCodePoint); + while (i < length) { + int charStart = i; + int codePointSize = getCodePointSize(text, length, i, pCodePoint); int codePoint = pCodePoint.get(0); - if (Character.isSpaceChar(codePoint)) { - lastSpace = i; - lastSpaceWidth = lineWidth; + int charEnd = i + codePointSize; + + if (codePoint == '\n') { + maxLineWidth = + addLine(lines, text, lineStart, charStart, currentWidth, fontMetrics, maxLineWidth); + lineCount++; + lineStart = charEnd; + currentWidth = 0; + lastSpace = -1; + lastSpaceEnd = -1; + lastSpaceWidth = 0; + i = charEnd; + continue; } - i += codePointSize; - // get char width stbtt_GetCodepointHMetrics(fontInfo, codePoint, pAdvance, pLeftSideBearing); float charWidth = pAdvance.get(0) * scaleFactor; - if (lineWidth + charWidth > maxWidth) { - lineEnd = wordWrap && lastSpace >= lineStart ? lastSpace + 1 : newLineStart; - textMetrics.line( - textLineMetrics - .characters(text.subSequence(lineStart, lineEnd)) - .height(fullLineHeight) - .width(lineWidth) - .build()); - + if (currentWidth + charWidth > maxWidth && lineStart < charStart) { + int lineEnd = charStart; + int nextLineStart = charStart; + float measuredWidth = currentWidth; + if (wordWrap && lastSpace >= lineStart) { + lineEnd = lastSpace; + nextLineStart = lastSpaceEnd; + measuredWidth = lastSpaceWidth; + } + maxLineWidth = + addLine(lines, text, lineStart, lineEnd, measuredWidth, fontMetrics, maxLineWidth); lineCount++; - textLineMetrics = - TextLineMetrics.builder().height(fullLineHeight).width(lineWidth - lastSpaceWidth); - - lineStart = lineEnd; - lineWidth = lineWidth - lastSpaceWidth; + lineStart = nextLineStart; + currentWidth = 0; + lastSpace = -1; + lastSpaceEnd = -1; lastSpaceWidth = 0; + i = nextLineStart; + continue; } - lineWidth += charWidth; + if (Character.isSpaceChar(codePoint)) { + lastSpace = charStart; + lastSpaceEnd = charEnd; + lastSpaceWidth = currentWidth; + } + currentWidth += charWidth; + i = charEnd; } - textMetrics.line( - textLineMetrics - .characters(text.subSequence(lineStart, lineEnd)) - .height(fullLineHeight) - .width(lineWidth) - .build()); - textMetrics.height(lineCount * fullLineHeight).fullLineHeight(fullLineHeight); - return textMetrics.build(); + maxLineWidth = addLine(lines, text, lineStart, length, currentWidth, fontMetrics, maxLineWidth); + lineCount++; + + return TextMetrics.builder() + .lines(lines) + .width(maxLineWidth) + .height(lineCount * fontMetrics.lineHeight()) + .lineHeight(fontMetrics.lineHeight()) + .fontMetrics(fontMetrics) + .build(); } } + @Override + public TextMetrics getTextMetrics( + @NonNull String text, + float offsetX, + @NonNull Font font, + float fontSize, + float lineHeight, + float maxWidth, + boolean wordWrap) { + return measureText(text, offsetX, font, fontSize, lineHeight, maxWidth, wordWrap); + } + @Override public FontMetrics getFontMetrics(@NonNull Font font, float fontSize, float lineHeight) { - STBTTFontinfo fontInfo = getFontInfo(font.path()); + return measureText("", font, fontSize, lineHeight).fontMetrics(); + } + + @Override + public TextLineMetrics getTextLineMetrics( + @NonNull String text, @NonNull Font font, float fontSize, float lineHeight) { + return measureText(text, font, fontSize, lineHeight).lines().get(0); + } + + private TextMetrics emptyTextMetrics(Font font, float fontSize, float lineHeight) { + FontMetrics fontMetrics = measureFontMetrics(getFontInfo(font.path()), fontSize, lineHeight); + return TextMetrics.builder() + .width(0) + .height(0) + .lineHeight(fontMetrics.lineHeight()) + .fontMetrics(fontMetrics) + .build(); + } + + private FontMetrics measureFontMetrics(STBTTFontinfo fontInfo, float fontSize, float lineHeight) { try (MemoryStack stack = MemoryStack.stackPush()) { IntBuffer ascent = stack.mallocInt(1); IntBuffer descent = stack.mallocInt(1); @@ -212,48 +261,40 @@ public FontMetrics getFontMetrics(@NonNull Font font, float fontSize, float line float metricsAscent = ascent.get(0) * scaleFactor; float metricsDescent = Math.abs(descent.get(0) * scaleFactor); float metricsLineGap = Math.max(0, lineGap.get(0) * scaleFactor); - float naturalLineHeight = metricsAscent + metricsDescent + metricsLineGap; - if (requestedLineHeight > naturalLineHeight) { - metricsLineGap += requestedLineHeight - naturalLineHeight; - } + float measuredLineHeight = Math.max(requestedLineHeight, metricsAscent + metricsDescent + metricsLineGap); if (roundToPixel) { metricsAscent = Math.round(metricsAscent); metricsDescent = Math.round(metricsDescent); metricsLineGap = Math.round(metricsLineGap); + measuredLineHeight = Math.round(measuredLineHeight); } - return new FontMetrics(metricsAscent, metricsDescent, metricsLineGap); + return new FontMetrics( + metricsAscent, metricsDescent, metricsLineGap, measuredLineHeight, metricsAscent); } } - @Override - public TextLineMetrics getTextLineMetrics( - @NonNull String text, @NonNull Font font, float fontSize, float lineHeight) { - STBTTFontinfo fontInfo = getFontInfo(font.path()); - try (MemoryStack stack = MemoryStack.stackPush()) { - IntBuffer pCodePoint = stack.mallocInt(1); - IntBuffer pAdvance = stack.mallocInt(1); - IntBuffer pLeftSideBearing = stack.mallocInt(1); - - float scaleFactor = stbtt_ScaleForMappingEmToPixels(fontInfo, fontSize); - - int textLength = text.length(); - - float lineWidth = 0; - int i = 0; - while (i < textLength) { - i += getCodePointSize(text, textLength, i, pCodePoint); - int codePoint = pCodePoint.get(0); - - // get char width - stbtt_GetCodepointHMetrics(fontInfo, codePoint, pAdvance, pLeftSideBearing); - lineWidth += pAdvance.get(0) * scaleFactor; - } - return TextLineMetrics.builder() - .height(fontSize * lineHeight) - .width(lineWidth) - .characters(text) - .build(); - } + private float addLine( + List lines, + String text, + int startIndex, + int endIndex, + float width, + FontMetrics fontMetrics, + float currentMaxWidth) { + int safeStart = Math.max(0, Math.min(startIndex, text.length())); + int safeEnd = Math.max(safeStart, Math.min(endIndex, text.length())); + lines.add( + TextLineMetrics.builder() + .characters(text.subSequence(safeStart, safeEnd)) + .startIndex(safeStart) + .endIndex(safeEnd) + .charCount(safeEnd - safeStart) + .width(width) + .height(fontMetrics.lineHeight()) + .baseline(fontMetrics.baseline()) + .fontMetrics(fontMetrics) + .build()); + return Math.max(currentMaxWidth, width); } // obtains font info from the map or if map has no entry, creates it and adds it to the map diff --git a/core/src/test/java/com/spinyowl/spinygui/core/layout/impl/BlockLayoutTest.java b/core/src/test/java/com/spinyowl/spinygui/core/layout/impl/BlockLayoutTest.java index 5d7f0097..bc16ed22 100644 --- a/core/src/test/java/com/spinyowl/spinygui/core/layout/impl/BlockLayoutTest.java +++ b/core/src/test/java/com/spinyowl/spinygui/core/layout/impl/BlockLayoutTest.java @@ -6,7 +6,6 @@ import com.spinyowl.spinygui.core.font.FontStyle; import com.spinyowl.spinygui.core.font.FontWeight; import com.spinyowl.spinygui.core.layout.ElementLayout; -import com.spinyowl.spinygui.core.layout.FontMetrics; import com.spinyowl.spinygui.core.layout.LayoutContext; import com.spinyowl.spinygui.core.layout.LayoutService; import com.spinyowl.spinygui.core.layout.TextMeasurer; @@ -25,6 +24,8 @@ import com.spinyowl.spinygui.core.style.types.border.BorderStyle; import com.spinyowl.spinygui.core.style.types.length.Length; import com.spinyowl.spinygui.core.style.types.length.Unit; +import com.spinyowl.spinygui.core.system.font.FontMetrics; +import com.spinyowl.spinygui.core.system.font.TextLineMetrics; import java.util.Set; import lombok.NonNull; import org.junit.jupiter.api.Test; @@ -127,13 +128,19 @@ public void layoutChildNodes(@NonNull Element element, @NonNull LayoutContext co private static class FixedTextMeasurer implements TextMeasurer { @Override - public float measure(@NonNull String text, @NonNull Font font, float fontSize) { - return text.length() * 10f; - } - - @Override - public FontMetrics metrics(@NonNull Font font, float fontSize, float lineHeight) { - return new FontMetrics(8, 2, Math.max(0, fontSize * lineHeight - 10)); + public TextLineMetrics measure( + @NonNull String text, @NonNull Font font, float fontSize, float lineHeight) { + FontMetrics fontMetrics = new FontMetrics(8, 2, Math.max(0, fontSize * lineHeight - 10), 10, 8); + return TextLineMetrics.builder() + .characters(text) + .startIndex(0) + .endIndex(text.length()) + .charCount(text.length()) + .width(text.length() * 10f) + .height(fontMetrics.lineHeight()) + .baseline(fontMetrics.baseline()) + .fontMetrics(fontMetrics) + .build(); } } } diff --git a/core/src/test/java/com/spinyowl/spinygui/core/layout/impl/InlineFormattingContextTest.java b/core/src/test/java/com/spinyowl/spinygui/core/layout/impl/InlineFormattingContextTest.java index 5cf50eb9..9017f8cf 100644 --- a/core/src/test/java/com/spinyowl/spinygui/core/layout/impl/InlineFormattingContextTest.java +++ b/core/src/test/java/com/spinyowl/spinygui/core/layout/impl/InlineFormattingContextTest.java @@ -5,7 +5,6 @@ import com.spinyowl.spinygui.core.font.Font; import com.spinyowl.spinygui.core.font.FontStyle; import com.spinyowl.spinygui.core.font.FontWeight; -import com.spinyowl.spinygui.core.layout.FontMetrics; import com.spinyowl.spinygui.core.layout.TextMeasurer; import com.spinyowl.spinygui.core.node.Element; import com.spinyowl.spinygui.core.node.NodeBuilder; @@ -18,6 +17,8 @@ import com.spinyowl.spinygui.core.style.types.TextAlign; import com.spinyowl.spinygui.core.style.types.WhiteSpace; import com.spinyowl.spinygui.core.style.types.length.Length; +import com.spinyowl.spinygui.core.system.font.FontMetrics; +import com.spinyowl.spinygui.core.system.font.TextLineMetrics; import java.util.List; import java.util.Set; import lombok.NonNull; @@ -27,10 +28,12 @@ class InlineFormattingContextTest { private InlineFormattingContext formattingContext; private Element parent; + private FixedTextMeasurer textMeasurer; @BeforeEach void setUp() { - formattingContext = new InlineFormattingContext(new FixedTextMeasurer()); + textMeasurer = new FixedTextMeasurer(); + formattingContext = new InlineFormattingContext(textMeasurer); parent = NodeBuilder.div(); style(parent, Display.BLOCK); parent.box().contentSize(50, 0); @@ -196,6 +199,17 @@ void layout_whenTextAlignRight_appliesExpectedOffset() { assertEquals(30, text.box().content().width()); } + @Test + void layout_whenUnitIsMeasured_usesSingleUnifiedMeasurementResult() { + parent.box().content().width(100); + Text text = NodeBuilder.text("abc"); + parent.addChild(text); + + formattingContext.layout(parent, List.of(text), 0); + + assertEquals(1, textMeasurer.measurementCount); + } + private void style(Element element, Display display) { ResolvedStyle style = element.resolvedStyle(); style.display(display); @@ -213,14 +227,23 @@ private void style(Element element, Display display) { } private static class FixedTextMeasurer implements TextMeasurer { - @Override - public float measure(@NonNull String text, @NonNull Font font, float fontSize) { - return text.length() * 10f; - } + private int measurementCount; @Override - public FontMetrics metrics(@NonNull Font font, float fontSize, float lineHeight) { - return new FontMetrics(8, 2, Math.max(0, fontSize * lineHeight - 10)); + public TextLineMetrics measure( + @NonNull String text, @NonNull Font font, float fontSize, float lineHeight) { + measurementCount++; + FontMetrics fontMetrics = new FontMetrics(8, 2, Math.max(0, fontSize * lineHeight - 10), 10, 8); + return TextLineMetrics.builder() + .characters(text) + .startIndex(0) + .endIndex(text.length()) + .charCount(text.length()) + .width(text.length() * 10f) + .height(fontMetrics.lineHeight()) + .baseline(fontMetrics.baseline()) + .fontMetrics(fontMetrics) + .build(); } } } diff --git a/core/src/test/java/com/spinyowl/spinygui/core/system/font/impl/FontServiceImplTest.java b/core/src/test/java/com/spinyowl/spinygui/core/system/font/impl/FontServiceImplTest.java new file mode 100644 index 00000000..0cbe15bd --- /dev/null +++ b/core/src/test/java/com/spinyowl/spinygui/core/system/font/impl/FontServiceImplTest.java @@ -0,0 +1,65 @@ +package com.spinyowl.spinygui.core.system.font.impl; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.spinyowl.spinygui.core.font.Font; +import com.spinyowl.spinygui.core.system.font.TextLineMetrics; +import com.spinyowl.spinygui.core.system.font.TextMetrics; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class FontServiceImplTest { + private FontServiceImpl fontService; + + @BeforeEach + void setUp() { + fontService = new FontServiceImpl(new FontStorageImpl(), false); + } + + @Test + void measureText_whenSingleLine_returnsWidthAndVerticalMetrics() { + TextMetrics metrics = fontService.measureText("abc", Font.DEFAULT, 16, 1.2f); + + assertEquals(1, metrics.lines().size()); + assertTrue(metrics.width() > 0); + assertTrue(metrics.height() > 0); + assertTrue(metrics.fontMetrics().ascent() > 0); + assertTrue(metrics.fontMetrics().descent() > 0); + assertEquals(metrics.fontMetrics().baseline(), metrics.lines().get(0).baseline()); + } + + @Test + void compatibilityWrappers_matchUnifiedApi() { + TextMetrics metrics = fontService.measureText("abc", Font.DEFAULT, 16, 1.2f); + TextLineMetrics line = fontService.getTextLineMetrics("abc", Font.DEFAULT, 16, 1.2f); + + assertEquals(metrics.lines().get(0), line); + assertEquals(metrics.fontMetrics(), fontService.getFontMetrics(Font.DEFAULT, 16, 1.2f)); + } + + @Test + void measureText_whenMaxWidthIsNearZero_returnsEmptyMetrics() { + TextMetrics metrics = fontService.measureText("abc", 0, Font.DEFAULT, 16, 1.2f, 0, true); + + assertTrue(metrics.lines().isEmpty()); + assertEquals(0, metrics.width()); + assertEquals(0, metrics.height()); + } + + @Test + void measureText_whenWrapped_preservesLineOrder() { + TextMetrics metrics = fontService.measureText("aa aa", 0, Font.DEFAULT, 16, 1.2f, 20, true); + + assertFalse(metrics.lines().isEmpty()); + assertEquals("aa", metrics.lines().get(0).characters().toString()); + } + + @Test + void measureText_whenWhitespace_reportsAdvance() { + TextMetrics metrics = fontService.measureText(" ", Font.DEFAULT, 16, 1.2f); + + assertTrue(metrics.width() > 0); + } +} diff --git a/docs/features/font-service-unified-text-metrics.md b/docs/features/font-service-unified-text-metrics.md new file mode 100644 index 00000000..24b6c65b --- /dev/null +++ b/docs/features/font-service-unified-text-metrics.md @@ -0,0 +1,47 @@ +# FontService Unified Text Metrics Plan + +## Summary +The ticket is only partially done. `FontService` now exposes vertical font metrics, but callers still need separate calls for text width and font metrics. Implement a unified text measurement API that returns horizontal and vertical metrics in one result, then update layout callers to use that single result. + +## Key Changes +- Add a font-system metric model under `core.system.font`: + - `FontMetrics`: `ascent`, `descent`, `lineGap`, `lineHeight`, `baseline`. + - `TextLineMetrics`: ordered line text, `startIndex`, `endIndex`, `charCount`, `width`, `height`, `baseline`, and `FontMetrics`. + - `TextMetrics`: ordered `List`, total `width`, total `height`, `lineHeight`, and top-level `FontMetrics`. +- Add a single primary API to `FontService`: + - `TextMetrics measureText(String text, Font font, float fontSize, float lineHeight)`. + - Keep wrapped measurement as `measureText(String text, float offsetX, Font font, float fontSize, float lineHeight, float maxWidth, boolean wordWrap)`. + - Keep `getTextLineMetrics()` and `getFontMetrics()` as active compatibility wrappers over the new API while the project is in active development. +- Update `FontServiceImpl`: + - Compute horizontal advance and vertical metrics in one STB-backed call path. + - Use one scale strategy consistently. + - Fix the current `maxWidth < 0.1` branch so it returns empty metrics immediately. + - Preserve line order with `List`, not `ImmutableSet`. +- Update layout integration: + - Change `TextMeasurer` to return unified line metrics from one method instead of separate `measure()` and `metrics()` calls. + - Update `InlineFormattingContext` to use the returned `width`, `lineHeight`, `baseline`, `ascent`, and `descent`. + - Remove or replace `core.layout.FontMetrics`; layout should not own font-system metric types. +- Rendering compatibility: + - Keep NanoVG rendering based on `InlineFragment`. + - Keep fragment `baseline`, `font`, `fontSize`, and `color`; add ascent/descent/lineHeight only if needed by renderer or hit testing. + +## Test Plan +- Add `FontServiceImpl` tests for: + - single-line measurement returns width and vertical metrics from one call; + - compatibility wrappers match the unified API; + - zero/near-zero max width returns empty metrics; + - wrapped metrics preserve line order; + - whitespace text still reports correct advance. +- Update inline layout tests: + - fake `TextMeasurer` returns unified metrics; + - assert layout uses one measurement result per text unit; + - keep existing wrapping, alignment, line-height, and fragment-union tests. +- Run: + - `./gradlew compileJava` + - `./gradlew compileTestJava` + - targeted font/layout tests once JaCoCo dependency resolution is fixed. + +## Assumptions +- This does not implement shaping, bidi, kerning, glyph atlas data, font fallback, or renderer-specific NanoVG APIs. +- "Useful for rendering" means baseline and vertical metrics are available with text width from one service call. +- Existing public methods remain during migration, but new layout code must use the unified API. From 8d338349be13722c712d62c2df45f00eacd32a01 Mon Sep 17 00:00:00 2001 From: SpinyOwl Date: Tue, 12 May 2026 02:52:58 +0200 Subject: [PATCH 005/121] Support decimal pixel values in CSS parser --- .../core/parser/impl/DefaultNodeParser.java | 7 ++++- .../css/visitor/PropertyValueVisitor.java | 6 ++-- .../com/spinyowl/spinygui/demo/small.html | 28 +++++++++++++++++++ 3 files changed, 37 insertions(+), 4 deletions(-) create mode 100644 demo.complex/src/main/resources/com/spinyowl/spinygui/demo/small.html diff --git a/core/src/main/java/com/spinyowl/spinygui/core/parser/impl/DefaultNodeParser.java b/core/src/main/java/com/spinyowl/spinygui/core/parser/impl/DefaultNodeParser.java index 82566684..d6bbe0c3 100644 --- a/core/src/main/java/com/spinyowl/spinygui/core/parser/impl/DefaultNodeParser.java +++ b/core/src/main/java/com/spinyowl/spinygui/core/parser/impl/DefaultNodeParser.java @@ -42,7 +42,11 @@ public Node fromHtml(String html) { if (frameElements.size() == 1) { return createNodeFromElement(frameElements.get(0), new NodeConverterContext()); } - return createNodeFromElement(document, new NodeConverterContext()); + var bodyChildren = document.body().children(); + if (bodyChildren.size() == 1) { + return createNodeFromElement(bodyChildren.first(), new NodeConverterContext()); + } + return createNodeFromElement(document.body(), new NodeConverterContext()); } private Node createNodeFromContent(org.jsoup.nodes.Node node, NodeConverterContext context) { @@ -174,6 +178,7 @@ private org.w3c.dom.Node createContent(org.w3c.dom.Document document, Node node) private org.w3c.dom.Element createElement(org.w3c.dom.Document document, Element node) { String name = node.nodeName().toLowerCase(); + log.debug("Creating element with name: {}", name); var element = document.createElement(name); for (var entry : node.attributes().entrySet()) { diff --git a/core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/visitor/PropertyValueVisitor.java b/core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/visitor/PropertyValueVisitor.java index 4fc8ab5a..58f77c93 100644 --- a/core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/visitor/PropertyValueVisitor.java +++ b/core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/visitor/PropertyValueVisitor.java @@ -38,7 +38,7 @@ public class PropertyValueVisitor extends CSS3BaseVisitor> { private static final String HSL = "hsl("; private static final String HSLA = "hsla("; - public static final String PIXEL_REGEX = "-?\\d+[pP][xX]"; + public static final String PIXEL_REGEX = "-?\\d+(\\.\\d+)?[pP][xX]"; private static Operator getOperator(Operator_Context op) { String operatorText = op != null ? op.getText() : " "; @@ -86,10 +86,10 @@ public Term visitDimension(DimensionContext ctx) { String dimensionText = ctx.getText(); if (dimensionText.matches(PIXEL_REGEX)) { return new TermLength( - Length.pixel(Integer.parseInt(dimensionText.substring(0, dimensionText.length() - 2)))); + Length.pixel(Float.parseFloat(dimensionText.substring(0, dimensionText.length() - 2)))); } - throw new NotImplementedException("The only dimension supported is pixel and percentage."); + throw new NotImplementedException("The only dimension supported is pixel and percentage. Value specified: " + dimensionText); } @Override diff --git a/demo.complex/src/main/resources/com/spinyowl/spinygui/demo/small.html b/demo.complex/src/main/resources/com/spinyowl/spinygui/demo/small.html new file mode 100644 index 00000000..5268b1be --- /dev/null +++ b/demo.complex/src/main/resources/com/spinyowl/spinygui/demo/small.html @@ -0,0 +1,28 @@ + +
+
c1 +
c11 Cfjg
+
c12
+
+
+ Hello world. Lorem ipsum dolor. + Hello world. Lorem ipsum dolor. + Hello world. Lorem ipsum dolor.s + Hello World +
+ +
+
+
+ Hello world. Lorem ipsum dolor. + Hello world A. Lorem ipsum dolor. + Hello world B. Lorem ipsum dolor. +
+
+
+
+
s1
+
s2
+
+
+
\ No newline at end of file From 6bee13f5389f1f8447997d8a0f9ebff406f03644 Mon Sep 17 00:00:00 2001 From: SpinyOwl Date: Sat, 16 May 2026 22:17:22 +0200 Subject: [PATCH 006/121] Replace gradle groovy DSL with kotlin DSL --- .gitattributes | 12 + .gitignore | 9 + AGENTS_CODE_STYLE.md | 2 +- README.md | 2 +- build.gradle | 74 ---- build.gradle.kts | 12 + buildSrc/build.gradle.kts | 13 + buildSrc/settings.gradle.kts | 14 + ...ic.java-application-conventions.gradle.kts | 11 + ...ldlogic.java-common-conventions.gradle.kts | 44 ++ ...dlogic.java-library-conventions.gradle.kts | 7 + core.backend.lwjgl.nanovg/build.gradle | 39 -- core.backend.lwjgl.nanovg/build.gradle.kts | 40 ++ core.backend/build.gradle | 24 - core.backend/build.gradle.kts | 19 + core/build.gradle | 73 ---- core/build.gradle.kts | 72 +++ .../impl/css/antlr/CSS3BaseListener.java | 2 +- .../impl/css/antlr/CSS3BaseVisitor.java | 2 +- .../core/parser/impl/css/antlr/CSS3Lexer.java | 21 +- .../parser/impl/css/antlr/CSS3Listener.java | 2 +- .../parser/impl/css/antlr/CSS3Parser.java | 32 +- .../parser/impl/css/antlr/CSS3Visitor.java | 2 +- demo.complex/build.gradle | 34 -- demo.complex/build.gradle.kts | 30 ++ demo.simple/build.gradle | 14 - demo.simple/build.gradle.kts | 11 + gradle.properties | 5 + gradle/libs.versions.toml | 61 +++ gradle/wrapper/gradle-wrapper.jar | Bin 62076 -> 48462 bytes gradle/wrapper/gradle-wrapper.properties | 5 +- gradlew | 33 +- gradlew.bat | 54 +-- settings.gradle | 11 - settings.gradle.kts | 21 + spinygui/build.gradle | 12 - spinygui/build.gradle.kts | 11 + tools/generate_structure_docs.js | 410 ------------------ 38 files changed, 478 insertions(+), 762 deletions(-) create mode 100644 .gitattributes delete mode 100644 build.gradle create mode 100644 build.gradle.kts create mode 100644 buildSrc/build.gradle.kts create mode 100644 buildSrc/settings.gradle.kts create mode 100644 buildSrc/src/main/kotlin/buildlogic.java-application-conventions.gradle.kts create mode 100644 buildSrc/src/main/kotlin/buildlogic.java-common-conventions.gradle.kts create mode 100644 buildSrc/src/main/kotlin/buildlogic.java-library-conventions.gradle.kts delete mode 100644 core.backend.lwjgl.nanovg/build.gradle create mode 100644 core.backend.lwjgl.nanovg/build.gradle.kts delete mode 100644 core.backend/build.gradle create mode 100644 core.backend/build.gradle.kts delete mode 100644 core/build.gradle create mode 100644 core/build.gradle.kts delete mode 100644 demo.complex/build.gradle create mode 100644 demo.complex/build.gradle.kts delete mode 100644 demo.simple/build.gradle create mode 100644 demo.simple/build.gradle.kts create mode 100644 gradle/libs.versions.toml delete mode 100644 settings.gradle create mode 100644 settings.gradle.kts delete mode 100644 spinygui/build.gradle create mode 100644 spinygui/build.gradle.kts delete mode 100644 tools/generate_structure_docs.js diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..f91f6460 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,12 @@ +# +# https://help.github.com/articles/dealing-with-line-endings/ +# +# Linux start script should use lf +/gradlew text eol=lf + +# These are Windows script files and should use crlf +*.bat text eol=crlf + +# Binary files should be left untouched +*.jar binary + diff --git a/.gitignore b/.gitignore index 003a85ff..a4d76839 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,12 @@ hs_err_pid* .project /.settings/ /lwjgl-extract/ + +# Ignore Gradle project-specific cache directory +.gradle + +# Ignore Gradle build output directory +build + +# Ignore Kotlin plugin data +.kotlin diff --git a/AGENTS_CODE_STYLE.md b/AGENTS_CODE_STYLE.md index 00b52f68..09b8236f 100644 --- a/AGENTS_CODE_STYLE.md +++ b/AGENTS_CODE_STYLE.md @@ -14,7 +14,7 @@ This project is a modular Java GUI library with a browser-engine-like architectu ## Java Style - Java modules are deliberate. Update `module-info.java` when adding exported APIs or new module dependencies. -- The build uses Java 21 source/target compatibility. README still says Java 15+, so verify before changing compatibility claims. +- The build uses Java 25 source/target compatibility. - Formatting follows the checked-in IntelliJ Google Java style. Keep two-space indentation and readable wrapped method chains. - Lombok is part of the project style. Existing code uses `@Getter`, `@Setter`, `@RequiredArgsConstructor`, `@NonNull`, `@ToString`, `@NoArgsConstructor`, and `@Slf4j`. - Lombok accessors are fluent and not chained. Prefer `element.box()` over `getBox()` and avoid introducing chained setter assumptions. diff --git a/README.md b/README.md index 841971a5..43f539bd 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ New implementation should be more flexible. ## System requirements -SpinyGUI requires Java 15+. +SpinyGUI requires Java 25+. ## Links diff --git a/build.gradle b/build.gradle deleted file mode 100644 index 82a3854b..00000000 --- a/build.gradle +++ /dev/null @@ -1,74 +0,0 @@ -plugins { - id "org.sonarqube" version "$sonarqube_version" - id "net.nemerosa.versioning" version "3.0.0" - id "checkstyle" -} - -project.group = 'com.spinyowl' - -printProjectInfo() - -private void printProjectInfo() { - println "##################################################" - println "# Build info: #" - println sprintf("# Project group: %-25s#", project.group) - println sprintf("# Project name: %-25s#", project.name) - println sprintf("# Project version: %-25s#", project.version) - println sprintf("# Java version: %-25s#", JavaVersion.current()) - println "##################################################" -} - -subprojects { - group = 'com.spinyowl' - version = rootProject.version - - // replace with new api - getLayout().getBuildDirectory().set(new File(rootProject.projectDir, "build/${project.name}")) - - repositories { - mavenCentral() - maven { url "https://oss.sonatype.org/content/repositories/snapshots/" } - maven { url "https://raw.githubusercontent.com/SpinyOwl/repo/releases" } - } - - plugins.withType(JavaLibraryPlugin).configureEach { - java { - modularity.inferModulePath = true - } - - sourceCompatibility = JavaVersion.VERSION_21 - targetCompatibility = JavaVersion.VERSION_21 - - compileJava.options.encoding = "UTF-8" - compileTestJava.options.encoding = "UTF-8" - javadoc.options.encoding = 'UTF-8' - - dependencies { - api group: 'org.projectlombok', name: 'lombok', version: lombok_version - annotationProcessor group: 'org.projectlombok', name: 'lombok', version: lombok_version - - testCompileOnly group: 'org.projectlombok', name: 'lombok', version: lombok_version - testAnnotationProcessor group: 'org.projectlombok', name: 'lombok', version: lombok_version - - // api - // implementation - testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: junit_version - testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-params', version: junit_version - testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: junit_version - } - } - - tasks.withType(Test).configureEach { - useJUnitPlatform() - } -} - -sonarqube { - properties { - property "sonar.projectKey", "SpinyOwl_SpinyGUI" - property "sonar.java.source", "17" - property "sonar.organization", "spinyowl" - property "sonar.host.url", "https://sonarcloud.io" - property "sonar.branch.name", versioning.info.branch - } -} diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 00000000..b7157bcb --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,12 @@ +plugins { + id("buildlogic.java-application-conventions") +} + +group = "com.spinyowl" + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(25) + } +} + diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts new file mode 100644 index 00000000..01878478 --- /dev/null +++ b/buildSrc/build.gradle.kts @@ -0,0 +1,13 @@ +/* + * This file was generated by the Gradle 'init' task. + */ + +plugins { + // Support convention plugins written in Kotlin. Convention plugins are build scripts in 'src/main' that automatically become available as plugins in the main build. + `kotlin-dsl` +} + +repositories { + // Use the plugin portal to apply community plugins in convention plugins. + gradlePluginPortal() +} diff --git a/buildSrc/settings.gradle.kts b/buildSrc/settings.gradle.kts new file mode 100644 index 00000000..31bf56aa --- /dev/null +++ b/buildSrc/settings.gradle.kts @@ -0,0 +1,14 @@ +/* + * This file was generated by the Gradle 'init' task. + * + * This settings file is used to specify which projects to include in your build-logic build. + */ + +dependencyResolutionManagement { + // Reuse version catalog from the main build. + versionCatalogs { + create("libs", { from(files("../gradle/libs.versions.toml")) }) + } +} + +rootProject.name = "buildSrc" diff --git a/buildSrc/src/main/kotlin/buildlogic.java-application-conventions.gradle.kts b/buildSrc/src/main/kotlin/buildlogic.java-application-conventions.gradle.kts new file mode 100644 index 00000000..b9b34c9b --- /dev/null +++ b/buildSrc/src/main/kotlin/buildlogic.java-application-conventions.gradle.kts @@ -0,0 +1,11 @@ +/* + * This file was generated by the Gradle 'init' task. + */ + +plugins { + // Apply the common convention plugin for shared build configuration between library and application projects. + id("buildlogic.java-common-conventions") + + // Apply the application plugin to add support for building a CLI application in Java. + application +} diff --git a/buildSrc/src/main/kotlin/buildlogic.java-common-conventions.gradle.kts b/buildSrc/src/main/kotlin/buildlogic.java-common-conventions.gradle.kts new file mode 100644 index 00000000..76cc6c0f --- /dev/null +++ b/buildSrc/src/main/kotlin/buildlogic.java-common-conventions.gradle.kts @@ -0,0 +1,44 @@ +val libs = extensions.getByType().named("libs") + +plugins { + java + `java-library` +} + +repositories { + mavenCentral() + maven { url = uri("https://oss.sonatype.org/content/repositories/snapshots/") } + maven { url = uri("https://raw.githubusercontent.com/SpinyOwl/repo/releases") } +} + +dependencies { + implementation(libs.findLibrary("slf4j").get()) + implementation(libs.findLibrary("logback").get()) + + compileOnly(libs.findLibrary("lombok").get()) + annotationProcessor(libs.findLibrary("lombok").get()) + + testImplementation(libs.findLibrary("junit").get()) + testRuntimeOnly("org.junit.platform:junit-platform-launcher") + + testCompileOnly(libs.findLibrary("lombok").get()) + testAnnotationProcessor(libs.findLibrary("lombok").get()) +} + +java { + modularity.inferModulePath = true + toolchain { + languageVersion = JavaLanguageVersion.of(25) + } + + withJavadocJar() + withSourcesJar() +} + +tasks.withType { + options.release.set(25) +} + +tasks.withType { + useJUnitPlatform() +} diff --git a/buildSrc/src/main/kotlin/buildlogic.java-library-conventions.gradle.kts b/buildSrc/src/main/kotlin/buildlogic.java-library-conventions.gradle.kts new file mode 100644 index 00000000..a30eefe3 --- /dev/null +++ b/buildSrc/src/main/kotlin/buildlogic.java-library-conventions.gradle.kts @@ -0,0 +1,7 @@ +/* + * This file was generated by the Gradle 'init' task. + */ + +plugins { + id("buildlogic.java-common-conventions") +} diff --git a/core.backend.lwjgl.nanovg/build.gradle b/core.backend.lwjgl.nanovg/build.gradle deleted file mode 100644 index ccb10fec..00000000 --- a/core.backend.lwjgl.nanovg/build.gradle +++ /dev/null @@ -1,39 +0,0 @@ -plugins { - id 'java-library' - id 'jacoco' -} -dependencies { - api project(':core') - api project(':core.backend') - - api group: 'org.lwjgl', name: 'lwjgl', version: lwjgl_version - api group: 'org.lwjgl', name: 'lwjgl', version: lwjgl_version, classifier: 'natives-windows' - api group: 'org.lwjgl', name: 'lwjgl', version: lwjgl_version, classifier: 'natives-linux' - api group: 'org.lwjgl', name: 'lwjgl', version: lwjgl_version, classifier: 'natives-macos' - - api group: 'org.lwjgl', name: 'lwjgl-glfw', version: lwjgl_version - api group: 'org.lwjgl', name: 'lwjgl-glfw', version: lwjgl_version, classifier: 'natives-windows' - api group: 'org.lwjgl', name: 'lwjgl-glfw', version: lwjgl_version, classifier: 'natives-linux' - api group: 'org.lwjgl', name: 'lwjgl-glfw', version: lwjgl_version, classifier: 'natives-macos' - - api group: 'org.lwjgl', name: 'lwjgl-opengl', version: lwjgl_version - api group: 'org.lwjgl', name: 'lwjgl-opengl', version: lwjgl_version, classifier: 'natives-windows' - api group: 'org.lwjgl', name: 'lwjgl-opengl', version: lwjgl_version, classifier: 'natives-linux' - api group: 'org.lwjgl', name: 'lwjgl-opengl', version: lwjgl_version, classifier: 'natives-macos' - - api group: 'org.lwjgl', name: 'lwjgl-nanovg', version: lwjgl_version - api group: 'org.lwjgl', name: 'lwjgl-nanovg', version: lwjgl_version, classifier: 'natives-windows' - api group: 'org.lwjgl', name: 'lwjgl-nanovg', version: lwjgl_version, classifier: 'natives-linux' - api group: 'org.lwjgl', name: 'lwjgl-nanovg', version: lwjgl_version, classifier: 'natives-macos' - - api group: 'org.slf4j', name: 'slf4j-api', version: slf4j_version - api group: 'ch.qos.logback', name: 'logback-classic', version: logback_version -} - -test { - finalizedBy jacocoTestReport // report is always generated after tests run -} -jacocoTestReport { - dependsOn test // tests are required to run before generating the report - reports.xml.required = true -} \ No newline at end of file diff --git a/core.backend.lwjgl.nanovg/build.gradle.kts b/core.backend.lwjgl.nanovg/build.gradle.kts new file mode 100644 index 00000000..a0d1bdde --- /dev/null +++ b/core.backend.lwjgl.nanovg/build.gradle.kts @@ -0,0 +1,40 @@ +plugins { + id("buildlogic.java-library-conventions") + id("jacoco") +} + +dependencies { + api(project(":core")) + api(project(":core.backend")) + + api(libs.lwjgl) + api(variantOf(libs.lwjgl) { classifier("natives-windows") }) + api(variantOf(libs.lwjgl) { classifier("natives-linux") }) + api(variantOf(libs.lwjgl) { classifier("natives-macos") }) + + api(libs.lwjglGlfw) + api(variantOf(libs.lwjglGlfw) { classifier("natives-windows") }) + api(variantOf(libs.lwjglGlfw) { classifier("natives-linux") }) + api(variantOf(libs.lwjglGlfw) { classifier("natives-macos") }) + + api(libs.lwjglOpengl) + api(variantOf(libs.lwjglOpengl) { classifier("natives-windows") }) + api(variantOf(libs.lwjglOpengl) { classifier("natives-linux") }) + api(variantOf(libs.lwjglOpengl) { classifier("natives-macos") }) + + api(libs.lwjglNanovg) + api(variantOf(libs.lwjglNanovg) { classifier("natives-windows") }) + api(variantOf(libs.lwjglNanovg) { classifier("natives-linux") }) + api(variantOf(libs.lwjglNanovg) { classifier("natives-macos") }) +} + +tasks.test { + finalizedBy(tasks.named("jacocoTestReport")) +} + +tasks.named("jacocoTestReport") { + dependsOn(tasks.test) + reports { + xml.required.set(true) + } +} diff --git a/core.backend/build.gradle b/core.backend/build.gradle deleted file mode 100644 index 86a20f10..00000000 --- a/core.backend/build.gradle +++ /dev/null @@ -1,24 +0,0 @@ -plugins { - id 'java-library' - id 'jacoco' -} -dependencies { - api project(':core') - - api group: 'org.slf4j', name: 'slf4j-api', version: slf4j_version - api group: 'ch.qos.logback', name: 'logback-classic', version: logback_version -} - -java { - withJavadocJar() - withSourcesJar() -} - -test { - finalizedBy jacocoTestReport // report is always generated after tests run -} - -jacocoTestReport { - dependsOn test // tests are required to run before generating the report - reports.xml.required = true -} diff --git a/core.backend/build.gradle.kts b/core.backend/build.gradle.kts new file mode 100644 index 00000000..221a3e1a --- /dev/null +++ b/core.backend/build.gradle.kts @@ -0,0 +1,19 @@ +plugins { + id("buildlogic.java-library-conventions") + id("jacoco") +} + +dependencies { + api(project(":core")) +} + +tasks.test { + finalizedBy(tasks.named("jacocoTestReport")) +} + +tasks.named("jacocoTestReport") { + dependsOn(tasks.test) + reports { + xml.required.set(true) + } +} diff --git a/core/build.gradle b/core/build.gradle deleted file mode 100644 index 820790a5..00000000 --- a/core/build.gradle +++ /dev/null @@ -1,73 +0,0 @@ -plugins { - id 'java-library' - id 'jacoco' - id 'antlr' -} - -dependencies { - antlr group: 'org.antlr', name: 'antlr4', version: antlr4_version - - // https://mvnrepository.com/artifact/commons-io/commons-io - implementation group: 'commons-io', name: 'commons-io', version: commons_io_version - - // https://mvnrepository.com/artifact/com.google.guava/guava - api group: 'com.google.guava', name: 'guava', version: guava_version - - // https://mvnrepository.com/artifact/org.jsoup/jsoup - implementation group: 'org.jsoup', name: 'jsoup', version: jsoup_version - - api group: 'org.slf4j', name: 'slf4j-api', version: slf4j_version - api group: 'ch.qos.logback', name: 'logback-classic', version: logback_version - api group: 'org.apache.commons', name: 'commons-lang3', version: commons_lang_version - - api group: 'org.joml', name: 'joml', version: joml_version - api group: 'org.antlr', name: 'antlr4-runtime', version: antlr4_version - - api group: 'io.github.classgraph', name: 'classgraph', version: classgraph_version - - api group: 'org.lwjgl', name: 'lwjgl', version: lwjgl_version - api group: 'org.lwjgl', name: 'lwjgl', version: lwjgl_version, classifier: 'natives-windows' - api group: 'org.lwjgl', name: 'lwjgl', version: lwjgl_version, classifier: 'natives-linux' - api group: 'org.lwjgl', name: 'lwjgl', version: lwjgl_version, classifier: 'natives-macos' - - api group: 'org.lwjgl', name: 'lwjgl-stb', version: lwjgl_version - api group: 'org.lwjgl', name: 'lwjgl-stb', version: lwjgl_version, classifier: 'natives-windows' - api group: 'org.lwjgl', name: 'lwjgl-stb', version: lwjgl_version, classifier: 'natives-linux' - api group: 'org.lwjgl', name: 'lwjgl-stb', version: lwjgl_version, classifier: 'natives-macos' - - api group: 'org.lwjgl', name: 'lwjgl-yoga', version: lwjgl_version - api group: 'org.lwjgl', name: 'lwjgl-yoga', version: lwjgl_version, classifier: 'natives-windows' - api group: 'org.lwjgl', name: 'lwjgl-yoga', version: lwjgl_version, classifier: 'natives-linux' - api group: 'org.lwjgl', name: 'lwjgl-yoga', version: lwjgl_version, classifier: 'natives-macos' - - // https://mvnrepository.com/artifact/org.mockito/mockito-core - testImplementation group: 'org.mockito', name: 'mockito-core', version: mockito_version - // https://mvnrepository.com/artifact/org.mockito/mockito-junit-jupiter - testImplementation group: 'org.mockito', name: 'mockito-junit-jupiter', version: mockito_version -} - -java { - withJavadocJar() - withSourcesJar() -} - -sourcesJar.dependsOn generateGrammarSource - -generateGrammarSource { - arguments = [ - "-listener", - "-visitor", - '-long-messages', - "-package", "com.spinyowl.spinygui.core.parser.impl.css.antlr", - ] - outputDirectory = file("src/main/java") -} -compileJava.dependsOn generateGrammarSource - -test { - finalizedBy jacocoTestReport // report is always generated after tests run -} -jacocoTestReport { - dependsOn test // tests are required to run before generating the report - reports.xml.required = true -} diff --git a/core/build.gradle.kts b/core/build.gradle.kts new file mode 100644 index 00000000..92211530 --- /dev/null +++ b/core/build.gradle.kts @@ -0,0 +1,72 @@ +plugins { + id("buildlogic.java-library-conventions") + id("jacoco") + id("antlr") +} + +dependencies { + antlr(libs.antlr) + + api(libs.guava) + api(libs.joml) + + implementation(libs.commonsIo) + implementation(libs.jsoup) + implementation(libs.commonsLang3) + + api(libs.antlrRuntime) + api(libs.classgraph) + + api(libs.lwjgl) + api(variantOf(libs.lwjgl) { classifier("natives-windows") }) + api(variantOf(libs.lwjgl) { classifier("natives-linux") }) + api(variantOf(libs.lwjgl) { classifier("natives-macos") }) + + api(libs.lwjglStb) + api(variantOf(libs.lwjglStb) { classifier("natives-windows") }) + api(variantOf(libs.lwjglStb) { classifier("natives-linux") }) + api(variantOf(libs.lwjglStb) { classifier("natives-macos") }) + + api(libs.lwjglYoga) + api(variantOf(libs.lwjglYoga) { classifier("natives-windows") }) + api(variantOf(libs.lwjglYoga) { classifier("natives-linux") }) + api(variantOf(libs.lwjglYoga) { classifier("natives-macos") }) + + implementation(libs.gson) + implementation(libs.snakeYaml) + + testImplementation(libs.junit) + testImplementation(libs.junitParams) + testImplementation(libs.mockito) + testImplementation(libs.mockitoJunitJupiter) +} + +tasks.generateGrammarSource { + arguments = listOf( + "-listener", + "-visitor", + "-long-messages", + "-package", "com.spinyowl.spinygui.core.parser.impl.css.antlr" + ) + outputDirectory = file("src/main/java") +} + +tasks.named("sourcesJar") { + dependsOn(tasks.generateGrammarSource) + duplicatesStrategy = DuplicatesStrategy.EXCLUDE +} + +tasks.compileJava { + dependsOn(tasks.generateGrammarSource) +} + +tasks.test { + finalizedBy(tasks.named("jacocoTestReport")) +} + +tasks.named("jacocoTestReport") { + dependsOn(tasks.test) + reports { + xml.required.set(true) + } +} diff --git a/core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3BaseListener.java b/core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3BaseListener.java index 033cbded..3ec270e1 100644 --- a/core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3BaseListener.java +++ b/core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3BaseListener.java @@ -1,4 +1,4 @@ -// Generated from com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3.g4 by ANTLR 4.13.1 +// Generated from com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3.g4 by ANTLR 4.13.2 package com.spinyowl.spinygui.core.parser.impl.css.antlr; import org.antlr.v4.runtime.ParserRuleContext; diff --git a/core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3BaseVisitor.java b/core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3BaseVisitor.java index 37b74508..06e7cee2 100644 --- a/core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3BaseVisitor.java +++ b/core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3BaseVisitor.java @@ -1,4 +1,4 @@ -// Generated from com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3.g4 by ANTLR 4.13.1 +// Generated from com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3.g4 by ANTLR 4.13.2 package com.spinyowl.spinygui.core.parser.impl.css.antlr; import org.antlr.v4.runtime.tree.AbstractParseTreeVisitor; diff --git a/core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3Lexer.java b/core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3Lexer.java index 5d7f2b41..8a0ed0d1 100644 --- a/core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3Lexer.java +++ b/core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3Lexer.java @@ -1,17 +1,22 @@ -// Generated from com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3.g4 by ANTLR 4.13.1 +// Generated from com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3.g4 by ANTLR 4.13.2 package com.spinyowl.spinygui.core.parser.impl.css.antlr; -import org.antlr.v4.runtime.Lexer; + import org.antlr.v4.runtime.CharStream; -import org.antlr.v4.runtime.Token; -import org.antlr.v4.runtime.TokenStream; -import org.antlr.v4.runtime.*; -import org.antlr.v4.runtime.atn.*; +import org.antlr.v4.runtime.Lexer; +import org.antlr.v4.runtime.RuntimeMetaData; +import org.antlr.v4.runtime.Vocabulary; +import org.antlr.v4.runtime.VocabularyImpl; +import org.antlr.v4.runtime.atn.ATN; +import org.antlr.v4.runtime.atn.ATNDeserializer; +import org.antlr.v4.runtime.atn.LexerATNSimulator; +import org.antlr.v4.runtime.atn.PredictionContextCache; import org.antlr.v4.runtime.dfa.DFA; -import org.antlr.v4.runtime.misc.*; @SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast", "CheckReturnValue", "this-escape"}) public class CSS3Lexer extends Lexer { - static { RuntimeMetaData.checkVersion("4.13.1", RuntimeMetaData.VERSION); } + static { + RuntimeMetaData.checkVersion("4.13.2", RuntimeMetaData.VERSION); + } protected static final DFA[] _decisionToDFA; protected static final PredictionContextCache _sharedContextCache = diff --git a/core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3Listener.java b/core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3Listener.java index f3e819a6..b137d531 100644 --- a/core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3Listener.java +++ b/core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3Listener.java @@ -1,4 +1,4 @@ -// Generated from com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3.g4 by ANTLR 4.13.1 +// Generated from com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3.g4 by ANTLR 4.13.2 package com.spinyowl.spinygui.core.parser.impl.css.antlr; import org.antlr.v4.runtime.tree.ParseTreeListener; diff --git a/core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3Parser.java b/core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3Parser.java index 0411d805..3c0fadf2 100644 --- a/core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3Parser.java +++ b/core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3Parser.java @@ -1,17 +1,31 @@ -// Generated from com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3.g4 by ANTLR 4.13.1 +// Generated from com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3.g4 by ANTLR 4.13.2 package com.spinyowl.spinygui.core.parser.impl.css.antlr; -import org.antlr.v4.runtime.atn.*; + +import org.antlr.v4.runtime.NoViableAltException; +import org.antlr.v4.runtime.Parser; +import org.antlr.v4.runtime.ParserRuleContext; +import org.antlr.v4.runtime.RecognitionException; +import org.antlr.v4.runtime.RuntimeMetaData; +import org.antlr.v4.runtime.Token; +import org.antlr.v4.runtime.TokenStream; +import org.antlr.v4.runtime.Vocabulary; +import org.antlr.v4.runtime.VocabularyImpl; +import org.antlr.v4.runtime.atn.ATN; +import org.antlr.v4.runtime.atn.ATNDeserializer; +import org.antlr.v4.runtime.atn.ParserATNSimulator; +import org.antlr.v4.runtime.atn.PredictionContextCache; import org.antlr.v4.runtime.dfa.DFA; -import org.antlr.v4.runtime.*; -import org.antlr.v4.runtime.misc.*; -import org.antlr.v4.runtime.tree.*; +import org.antlr.v4.runtime.tree.ParseTreeListener; +import org.antlr.v4.runtime.tree.ParseTreeVisitor; +import org.antlr.v4.runtime.tree.TerminalNode; + import java.util.List; -import java.util.Iterator; -import java.util.ArrayList; -@SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast", "CheckReturnValue"}) +@SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast", "CheckReturnValue", "this-escape"}) public class CSS3Parser extends Parser { - static { RuntimeMetaData.checkVersion("4.13.1", RuntimeMetaData.VERSION); } + static { + RuntimeMetaData.checkVersion("4.13.2", RuntimeMetaData.VERSION); + } protected static final DFA[] _decisionToDFA; protected static final PredictionContextCache _sharedContextCache = diff --git a/core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3Visitor.java b/core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3Visitor.java index 44769f80..a13bf00f 100644 --- a/core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3Visitor.java +++ b/core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3Visitor.java @@ -1,4 +1,4 @@ -// Generated from com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3.g4 by ANTLR 4.13.1 +// Generated from com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3.g4 by ANTLR 4.13.2 package com.spinyowl.spinygui.core.parser.impl.css.antlr; import org.antlr.v4.runtime.tree.ParseTreeVisitor; diff --git a/demo.complex/build.gradle b/demo.complex/build.gradle deleted file mode 100644 index 91fe98d4..00000000 --- a/demo.complex/build.gradle +++ /dev/null @@ -1,34 +0,0 @@ -plugins { - id 'java-library' - id 'application' -} - -dependencies { - api project(':core') - - api project(':core.backend') - api project(':core.backend.lwjgl.nanovg') - - api group: 'com.spinyowl', name: 'cbchain', version: '1.0.2'; - - api group: 'org.slf4j', name: 'slf4j-api', version: slf4j_version - api group: 'ch.qos.logback', name: 'logback-classic', version: logback_version - - api group: 'org.lwjgl', name: 'lwjgl', version: lwjgl_version - api group: 'org.lwjgl', name: 'lwjgl', version: lwjgl_version, classifier: 'natives-windows' - api group: 'org.lwjgl', name: 'lwjgl', version: lwjgl_version, classifier: 'natives-linux' - api group: 'org.lwjgl', name: 'lwjgl', version: lwjgl_version, classifier: 'natives-macos' - - api group: 'org.lwjgl', name: 'lwjgl-glfw', version: lwjgl_version - api group: 'org.lwjgl', name: 'lwjgl-glfw', version: lwjgl_version, classifier: 'natives-windows' - api group: 'org.lwjgl', name: 'lwjgl-glfw', version: lwjgl_version, classifier: 'natives-linux' - api group: 'org.lwjgl', name: 'lwjgl-glfw', version: lwjgl_version, classifier: 'natives-macos' - - api group: 'org.lwjgl', name: 'lwjgl-opengl', version: lwjgl_version - api group: 'org.lwjgl', name: 'lwjgl-opengl', version: lwjgl_version, classifier: 'natives-windows' - api group: 'org.lwjgl', name: 'lwjgl-opengl', version: lwjgl_version, classifier: 'natives-linux' - api group: 'org.lwjgl', name: 'lwjgl-opengl', version: lwjgl_version, classifier: 'natives-macos' -} -application { - mainClass = "com.spinyowl.spinygui.demo.complex.Main" -} diff --git a/demo.complex/build.gradle.kts b/demo.complex/build.gradle.kts new file mode 100644 index 00000000..a01ddf7a --- /dev/null +++ b/demo.complex/build.gradle.kts @@ -0,0 +1,30 @@ +plugins { + id("buildlogic.java-application-conventions") +} + +dependencies { + api(project(":core")) + api(project(":core.backend")) + api(project(":core.backend.lwjgl.nanovg")) + + api(libs.cbchain) + + api(libs.lwjgl) + api(variantOf(libs.lwjgl) { classifier("natives-windows") }) + api(variantOf(libs.lwjgl) { classifier("natives-linux") }) + api(variantOf(libs.lwjgl) { classifier("natives-macos") }) + + api(libs.lwjglGlfw) + api(variantOf(libs.lwjglGlfw) { classifier("natives-windows") }) + api(variantOf(libs.lwjglGlfw) { classifier("natives-linux") }) + api(variantOf(libs.lwjglGlfw) { classifier("natives-macos") }) + + api(libs.lwjglOpengl) + api(variantOf(libs.lwjglOpengl) { classifier("natives-windows") }) + api(variantOf(libs.lwjglOpengl) { classifier("natives-linux") }) + api(variantOf(libs.lwjglOpengl) { classifier("natives-macos") }) +} + +application { + mainClass.set("com.spinyowl.spinygui.demo.complex.Main") +} diff --git a/demo.simple/build.gradle b/demo.simple/build.gradle deleted file mode 100644 index 868f8801..00000000 --- a/demo.simple/build.gradle +++ /dev/null @@ -1,14 +0,0 @@ -plugins { - id 'java-library' - id 'application' -} - -dependencies { - implementation project(':spinygui') - - api group: 'org.slf4j', name: 'slf4j-api', version: slf4j_version - api group: 'ch.qos.logback', name: 'logback-classic', version: logback_version -} -application { - mainClass = "com.spinyowl.spinygui.demo.simple.Main" -} diff --git a/demo.simple/build.gradle.kts b/demo.simple/build.gradle.kts new file mode 100644 index 00000000..68d58770 --- /dev/null +++ b/demo.simple/build.gradle.kts @@ -0,0 +1,11 @@ +plugins { + id("buildlogic.java-application-conventions") +} + +dependencies { + implementation(project(":spinygui")) +} + +application { + mainClass.set("com.spinyowl.spinygui.demo.simple.Main") +} diff --git a/gradle.properties b/gradle.properties index f6f8c7eb..6582118a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,8 @@ +# This file was generated by the Gradle 'init' task. +# https://docs.gradle.org/current/userguide/build_environment.html#sec:gradle_configuration_properties +org.gradle.configuration-cache=true + + version=0.0.1-SNAPSHOT # code generation antlr4_version=4.13.1 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 00000000..8e4b74b5 --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,61 @@ +# This file was generated by the Gradle 'init' task. +# https://docs.gradle.org/current/userguide/version_catalogs.html#sec::toml-dependencies-format +[versions] +antlr = "4.13.2" +cbchain = "1.0.2" +classgraph = "4.8.184" +commonsIo = "2.22.0" +commonsLang3 = "3.20.0" +gson = "2.14.0" +guava = "33.6.0-jre" +joml = "1.10.8" +jomlPrimitives = "1.10.0" +jsoup = "1.22.2" +junit = "6.0.3" +junitPlatform = "1.10.5" +logback = "1.5.32" +lombok = "1.18.46" +lwjgl = "3.4.1" +mockito = "5.23.0" +netty = "4.1.120.Final" +slf4j = "2.0.18" +snakeYaml = "2.6" + + +[libraries] +antlr = { module = "org.antlr:antlr4", version.ref = "antlr" } +antlrRuntime = { module = "org.antlr:antlr4-runtime", version.ref = "antlr" } +cbchain = { module = "com.spinyowl:cbchain", version.ref = "cbchain" } +classgraph = { module = "io.github.classgraph:classgraph", version.ref = "classgraph" } +commonsIo = { module = "commons-io:commons-io", version.ref = "commonsIo" } +commonsLang3 = { module = "org.apache.commons:commons-lang3", version.ref = "commonsLang3" } +gson = { module = "com.google.code.gson:gson", version.ref = "gson" } +guava = { module = "com.google.guava:guava", version.ref = "guava" } +joml = { module = "org.joml:joml", version.ref = "joml" } +jomlExperimental = { module = "org.joml:joml-experimental", version.ref = "joml" } +jomlPrimitives = { module = "org.joml:joml-primitives", version.ref = "jomlPrimitives" } +jsoup = { module = "org.jsoup:jsoup", version.ref = "jsoup" } +junit = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit" } +junitParams = { module = "org.junit.jupiter:junit-jupiter-params", version.ref = "junit" } +junitPlatformLauncher = { module = "org.junit.platform:junit-platform-launcher", version.ref = "junitPlatform" } +logback = { module = "ch.qos.logback:logback-classic", version.ref = "logback" } +lombok = { module = "org.projectlombok:lombok", version.ref = "lombok" } +lwjgl = { module = "org.lwjgl:lwjgl", version.ref = "lwjgl" } +lwjglGlfw = { module = "org.lwjgl:lwjgl-glfw", version.ref = "lwjgl" } +lwjglNanovg = { module = "org.lwjgl:lwjgl-nanovg", version.ref = "lwjgl" } +lwjglOpengl = { module = "org.lwjgl:lwjgl-opengl", version.ref = "lwjgl" } +lwjglStb = { module = "org.lwjgl:lwjgl-stb", version.ref = "lwjgl" } +lwjglYoga = { module = "org.lwjgl:lwjgl-yoga", version.ref = "lwjgl" } +mockito = { module = "org.mockito:mockito-core", version.ref = "mockito" } +mockitoJunitJupiter = { module = "org.mockito:mockito-junit-jupiter", version.ref = "mockito" } +netty = { module = "io.netty:netty-all", version.ref = "netty" } +slf4j = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" } +snakeYaml = { module = "org.yaml:snakeyaml", version.ref = "snakeYaml" } + + +[bundles] +lwjgl = [ + "lwjgl", + "lwjglStb", + "lwjglYoga" +] diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index c1962a79e29d3e0ab67b14947c167a862655af9b..b1b8ef56b44f16b14dc800fa8103a6d89abb526f 100644 GIT binary patch literal 48462 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 R2np~f1o-$>Q}y+){|A@R9n$~+ literal 62076 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index bb6c1919..df6a6ad7 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,9 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.1-bin.zip networkTimeout=10000 +retries=0 +retryBackOffMs=500 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index aeb74cbb..b9bb139f 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright © 2015-2021 the original authors. +# Copyright © 2015 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -83,7 +85,8 @@ done # This is normally unused # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -111,7 +114,6 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. @@ -130,10 +132,13 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. @@ -141,7 +146,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac @@ -149,7 +154,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -166,7 +171,6 @@ fi # For Cygwin or MSYS, switch paths to Windows format before running java if "$cygwin" || "$msys" ; then APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) JAVACMD=$( cygpath --unix "$JAVACMD" ) @@ -198,16 +202,15 @@ fi # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/gradlew.bat b/gradlew.bat index 93e3f59f..24c62d56 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -21,8 +23,8 @@ @rem @rem ########################################################################## -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal +@rem Set local scope for the variables, and ensure extensions are enabled +setlocal EnableExtensions set DIRNAME=%~dp0 if "%DIRNAME%"=="" set DIRNAME=. @@ -43,13 +45,13 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 -goto fail +"%COMSPEC%" /c exit 1 :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% @@ -57,36 +59,24 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 -goto fail +"%COMSPEC%" /c exit 1 :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal +@rem endlocal doesn't take effect until after the line is parsed and variables are expanded +@rem which allows us to clear the local environment before executing the java command +endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel -:omega +:exitWithErrorLevel +@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts +"%COMSPEC%" /c exit %ERRORLEVEL% diff --git a/settings.gradle b/settings.gradle deleted file mode 100644 index 0f672dbe..00000000 --- a/settings.gradle +++ /dev/null @@ -1,11 +0,0 @@ -rootProject.name = 'SpinyGUI' - -include 'spinygui' - -include 'core' - -include 'core.backend' -include 'core.backend.lwjgl.nanovg' - -include 'demo.simple' -include 'demo.complex' \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 00000000..8011c5f1 --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,21 @@ +/* + * This file was generated by the Gradle 'init' task. + * + * The settings file is used to specify which projects to include in your build. + * For more detailed information on multi-project builds, please refer to https://docs.gradle.org/9.5.1/userguide/multi_project_builds.html in the Gradle documentation. + */ + +plugins { + // Apply the foojay-resolver plugin to allow automatic download of JDKs + id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" +} + +rootProject.name = "SpinyGUI" +include( + "spinygui", + "core", + "core.backend", + "core.backend.lwjgl.nanovg", + "demo.simple", + "demo.complex" +) diff --git a/spinygui/build.gradle b/spinygui/build.gradle deleted file mode 100644 index 4c177fcf..00000000 --- a/spinygui/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -plugins { - id 'java-library' -} - -dependencies { - api project(':core') - - api project(':core.backend') - api project(':core.backend.lwjgl.nanovg') - - api group: 'com.spinyowl', name: 'cbchain', version: '1.0.2'; -} \ No newline at end of file diff --git a/spinygui/build.gradle.kts b/spinygui/build.gradle.kts new file mode 100644 index 00000000..e79f5bc8 --- /dev/null +++ b/spinygui/build.gradle.kts @@ -0,0 +1,11 @@ +plugins { + id("buildlogic.java-library-conventions") +} + +dependencies { + api(project(":core")) + api(project(":core.backend")) + api(project(":core.backend.lwjgl.nanovg")) + + api(libs.cbchain) +} diff --git a/tools/generate_structure_docs.js b/tools/generate_structure_docs.js deleted file mode 100644 index 1e873893..00000000 --- a/tools/generate_structure_docs.js +++ /dev/null @@ -1,410 +0,0 @@ -const fs = require("fs"); -const path = require("path"); - -const root = process.cwd(); -const outRoot = path.join(root, "docs", "project-structure"); -const packagesRoot = path.join(outRoot, "packages"); - -const roots = [ - "core/src/main/java", - "core/src/test/java", - "core.backend/src/main/java", - "core.backend.lwjgl.nanovg/src/main/java", - "demo.simple/src/main/java", - "demo.complex/src/main/java", - "spinygui/src/main/java", -]; - -const moduleDescriptions = { - core: "Core DOM-like node model, CSS parser/style system, layout, events, input, fonts, animation, and platform abstraction.", - "core.backend": "Renderer backend API shared by concrete rendering implementations.", - "core.backend.lwjgl.nanovg": "LWJGL/NanoVG renderer implementation for drawing the core scene graph.", - "demo.simple": "Small launcher-style examples for exercising the aggregate SpinyGUI module.", - "demo.complex": "GLFW/LWJGL demo harness and NanoVG example runner.", - spinygui: "Aggregate module that re-exports the core and default backend modules.", -}; - -const packageDescriptions = new Map(Object.entries({ - "com": "Top-level Java namespace folder for project packages.", - "com.spinyowl": "SpinyOwl namespace folder.", - "com.spinyowl.spinygui": "SpinyGUI namespace folder aggregating core, backend, and demo packages.", - "com.spinyowl.spinygui.core": "Top-level core configuration and shared entry points for the GUI engine.", - "com.spinyowl.spinygui.core.backend": "Backend namespace folder for renderer APIs and implementations.", - "com.spinyowl.spinygui.core.animation": "Frame-time animation contracts and a simple animator loop.", - "com.spinyowl.spinygui.core.clipboard": "Clipboard abstraction used by platform integrations.", - "com.spinyowl.spinygui.core.cursor": "Cursor model and cursor service abstraction.", - "com.spinyowl.spinygui.core.event": "Application-level events emitted to nodes and event targets.", - "com.spinyowl.spinygui.core.event.listener": "Generic event listener contract for application events.", - "com.spinyowl.spinygui.core.event.processor": "Dispatch logic for routing application events to node listeners.", - "com.spinyowl.spinygui.core.font": "CSS-like font value objects: family, size, stretch, style, and weight.", - "com.spinyowl.spinygui.core.image": "Image abstraction used by style and rendering layers.", - "com.spinyowl.spinygui.core.input": "Input domain model for keyboard, mouse, shortcuts, and user-facing key mappings.", - "com.spinyowl.spinygui.core.input.impl": "Default mutable implementations of input services.", - "com.spinyowl.spinygui.core.layout": "Layout contracts, layout context, and text/element layout interfaces.", - "com.spinyowl.spinygui.core.layout.impl": "Concrete layout algorithms and utilities for block, flex, none, text, and layout tree updates.", - "com.spinyowl.spinygui.core.node": "DOM-like node hierarchy: frames, elements, empty elements, text nodes, and builders.", - "com.spinyowl.spinygui.core.node.intersection": "Hit-testing strategy objects for node intersection checks.", - "com.spinyowl.spinygui.core.node.layout": "Box-model geometry value objects used by layout and rendering.", - "com.spinyowl.spinygui.core.parser": "Parser interfaces for HTML-like node trees and stylesheets.", - "com.spinyowl.spinygui.core.parser.impl": "Default parser implementations and parser factory code.", - "com.spinyowl.spinygui.core.parser.impl.css": "CSS parser namespace containing generated ANTLR artifacts and handwritten semantic visitors.", - "com.spinyowl.spinygui.core.parser.impl.css.antlr": "Generated ANTLR CSS3 lexer/parser/listener/visitor artifacts. Regenerate from the grammar instead of hand-editing.", - "com.spinyowl.spinygui.core.parser.impl.css.visitor": "ANTLR visitors that convert CSS parse trees into stylesheet, selector, declaration, and term model objects.", - "com.spinyowl.spinygui.core.style": "Resolved style state applied to nodes after rule matching and property conversion.", - "com.spinyowl.spinygui.core.style.manager": "Style manager contract and implementation for applying stylesheets to node trees.", - "com.spinyowl.spinygui.core.style.stylesheet": "CSS stylesheet domain model: properties, rulesets, declarations, terms, specificity, and provider registry.", - "com.spinyowl.spinygui.core.style.stylesheet.annotation": "Annotations used by stylesheet property providers.", - "com.spinyowl.spinygui.core.style.stylesheet.atrule": "CSS at-rule model objects.", - "com.spinyowl.spinygui.core.style.stylesheet.impl": "Default property-store implementation and provider scanner integration.", - "com.spinyowl.spinygui.core.style.stylesheet.property": "CSS property providers that parse declarations into typed style values.", - "com.spinyowl.spinygui.core.style.stylesheet.selector": "Selector contracts and base selector types.", - "com.spinyowl.spinygui.core.style.stylesheet.selector.combinator": "Combinator selectors for descendant, child, sibling, adjacent sibling, and compound matching.", - "com.spinyowl.spinygui.core.style.stylesheet.selector.pseudoclass": "Pseudo-class selector implementations.", - "com.spinyowl.spinygui.core.style.stylesheet.selector.pseudoelement": "Pseudo-element selector implementations.", - "com.spinyowl.spinygui.core.style.stylesheet.selector.simple": "Simple selectors for all, element, class, and id matching.", - "com.spinyowl.spinygui.core.style.stylesheet.term": "Typed CSS term values produced by parser visitors.", - "com.spinyowl.spinygui.core.style.stylesheet.util": "Utility functions for converting and validating stylesheet values.", - "com.spinyowl.spinygui.core.style.types": "Typed CSS value objects and constants for non-nested style domains.", - "com.spinyowl.spinygui.core.style.types.background": "Background-origin, repeat, and sizing value objects.", - "com.spinyowl.spinygui.core.style.types.border": "Border item and border-style value objects.", - "com.spinyowl.spinygui.core.style.types.flex": "Flexbox alignment, direction, wrapping, and justification value constants.", - "com.spinyowl.spinygui.core.style.types.length": "CSS length units, length wrappers, and conversion contract.", - "com.spinyowl.spinygui.core.system.event": "Raw platform/window/input events before conversion into application-level events.", - "com.spinyowl.spinygui.core.system.event.listener": "Adapters that translate raw system events into core event processing and state changes.", - "com.spinyowl.spinygui.core.system.event.processor": "System-event processor contract and implementation for dispatching platform events.", - "com.spinyowl.spinygui.core.system.event.provider": "Provider for mapping raw system event classes to listener instances.", - "com.spinyowl.spinygui.core.system.font": "Platform font loading, text metrics, and font storage abstractions.", - "com.spinyowl.spinygui.core.system.font.impl": "Default font service, storage, and platform-specific font directory discovery.", - "com.spinyowl.spinygui.core.system.input": "Platform-facing key, modifier, action, and mouse-button enums.", - "com.spinyowl.spinygui.core.time": "Time service abstraction for animation and frame timing.", - "com.spinyowl.spinygui.core.util": "Small utilities for class-key maps, IO, node visibility, references, and text handling.", - "com.spinyowl.spinygui.core.backend.renderer": "Renderer SPI consumed by backend implementations.", - "com.spinyowl.spinygui.core.backend.renderer.lwjgl.nanovg": "NanoVG renderer orchestration and specialized element/text/border renderers.", - "com.spinyowl.spinygui.core.backend.renderer.lwjgl.nanovg.util": "NanoVG drawing and color helpers.", - "com.spinyowl.spinygui.demo.simple": "Simple demo entry points.", - "com.spinyowl.spinygui.demo.complex": "Windowed GLFW/LWJGL demo framework and concrete NanoVG demo.", - "com.spinyowl.spinygui.demo": "Demo namespace folder for runnable examples.", -})); - -function walk(dir) { - if (!fs.existsSync(dir)) return []; - const entries = fs.readdirSync(dir, { withFileTypes: true }); - return entries.flatMap((entry) => { - const p = path.join(dir, entry.name); - if (entry.isDirectory()) return walk(p); - return [p]; - }); -} - -function rel(p) { - return path.relative(root, p).replace(/\\/g, "/"); -} - -function moduleName(file) { - const r = rel(file); - const first = r.split("/")[0]; - return first; -} - -function sourceSet(file) { - const r = rel(file); - if (r.includes("/src/test/")) return "test"; - if (r.includes("/src/main/antlr/")) return "main/antlr"; - return "main"; -} - -function extractJavadocs(text, index) { - const before = text.slice(0, index); - const match = before.match(/\/\*\*([\s\S]*?)\*\/\s*(?:@\w[^\n]*\s*)*$/); - if (!match) return ""; - const cleaned = match[1] - .split(/\r?\n/) - .map((line) => line.replace(/^\s*\*\s?/, "").trim()) - .filter((line) => line && !line.startsWith("@")) - .join(" ") - .replace(/\{@link\s+([^}]+)\}/g, "$1") - .replace(/<[^>]+>/g, "") - .replace(/\s+/g, " ") - .trim(); - const sentence = cleaned.match(/^(.+?[.!?])(?:\s|$)/); - return sentence ? sentence[1] : cleaned; -} - -function declarationSummary(name, kind, declaration, file) { - const base = path.basename(file); - if (name.startsWith("CSS3") && file.includes(`${path.sep}antlr${path.sep}`)) { - return "Generated ANTLR CSS3 parser artifact; regenerate it from CSS3.g4 rather than editing by hand."; - } - if (name.endsWith("Test")) return "JUnit test fixture for the corresponding production component."; - if (kind === "interface" && name.endsWith("Service")) return "Service contract for the related subsystem."; - if (kind === "interface" && name.endsWith("Provider")) return "Provider contract for supplying subsystem collaborators."; - if (kind === "interface" && name.endsWith("Parser")) return "Parser contract for converting external text into core model objects."; - if (kind === "interface" && name.endsWith("Renderer")) return "Renderer contract for drawing a frame through a backend."; - if (name.endsWith("Impl")) return "Default implementation of the matching interface."; - if (name.endsWith("Provider")) return "Provider implementation or registry for constructing subsystem collaborators."; - if (name.endsWith("Visitor")) return "ANTLR visitor that maps parse-tree nodes into the stylesheet domain model."; - if (name.endsWith("Selector")) return "CSS selector implementation used to match elements and calculate specificity."; - if (name.endsWith("PropertyProvider")) return "CSS property parser that converts declaration terms into typed style properties."; - if (name.endsWith("EventListener")) return "System-event listener/adapter for translating platform events into core behavior."; - if (name.endsWith("Event")) return "Event payload object for the named input/window/node change."; - if (name.endsWith("Layout")) return "Layout algorithm or layout contract for the named display/text mode."; - if (name.endsWith("Util") || name.endsWith("Utils") || name.endsWith("Utilities")) return "Static helper methods for the named subsystem."; - if (kind === "enum") return "Enumerates supported values for the named domain concept."; - if (declaration.includes("extends Event")) return "Application-level event payload."; - if (declaration.includes("extends SystemEvent")) return "Raw platform event payload."; - if (declaration.includes("implements Unit")) return "CSS unit/value object used by style conversion."; - if (base === "module-info.java") return "Java module descriptor."; - return `Represents ${name.replace(/([a-z])([A-Z])/g, "$1 $2").toLowerCase()} in this package.`; -} - -function parseJava(file) { - const text = fs.readFileSync(file, "utf8"); - const pkg = (text.match(/^\s*package\s+([\w.]+)\s*;/m) || [])[1]; - if (!pkg) return null; - const classes = []; - const textWithoutComments = text - .replace(/\/\*[\s\S]*?\*\//g, (m) => " ".repeat(m.length)) - .replace(/\/\/[^\n\r]*/g, (m) => " ".repeat(m.length)); - const depthText = textWithoutComments - .replace(/"(?:\\.|[^"\\])*"/g, (m) => " ".repeat(m.length)) - .replace(/'(?:\\.|[^'\\])*'/g, (m) => " ".repeat(m.length)); - const topLevelDepthAt = (index) => { - let depth = 0; - for (let i = 0; i < index; i++) { - const c = depthText[i]; - if (c === "{") depth++; - else if (c === "}") depth--; - } - return depth; - }; - const re = /((?:public|protected|private|abstract|final|static|sealed|non-sealed)\s+)*(class|interface|enum|record|@interface)\s+(\w+)([^{;]*)/g; - let m; - while ((m = re.exec(textWithoutComments))) { - if (topLevelDepthAt(m.index) !== 0) continue; - const kind = m[2] === "@interface" ? "annotation" : m[2]; - const name = m[3]; - const declaration = `${(m[1] || "").trim()} ${m[2]} ${name}${m[4] || ""}`.replace(/\s+/g, " ").trim(); - const javadoc = extractJavadocs(text, m.index); - classes.push({ - name, - kind, - declaration, - source: rel(file), - summary: javadoc || declarationSummary(name, kind, declaration, file), - }); - } - return { pkg, module: moduleName(file), sourceSet: sourceSet(file), classes }; -} - -const javaFiles = roots.flatMap((r) => walk(path.join(root, r))).filter((f) => f.endsWith(".java")); -const packageMap = new Map(); -for (const file of javaFiles) { - const parsed = parseJava(file); - if (!parsed) continue; - if (!packageMap.has(parsed.pkg)) { - packageMap.set(parsed.pkg, { pkg: parsed.pkg, modules: new Set(), sourceSets: new Set(), classes: [] }); - } - const pkg = packageMap.get(parsed.pkg); - pkg.modules.add(parsed.module); - pkg.sourceSets.add(parsed.sourceSet); - pkg.classes.push(...parsed.classes); -} - -for (const pkg of [...packageMap.keys()]) { - const segments = pkg.split("."); - for (let i = 1; i < segments.length; i++) { - const prefix = segments.slice(0, i).join("."); - if (!packageMap.has(prefix)) { - packageMap.set(prefix, { pkg: prefix, modules: new Set(), sourceSets: new Set(), classes: [] }); - } - } -} - -for (const pkg of [...packageMap.keys()]) { - const info = packageMap.get(pkg); - const segments = pkg.split("."); - for (let i = 1; i < segments.length; i++) { - const prefixInfo = packageMap.get(segments.slice(0, i).join(".")); - for (const module of info.modules) prefixInfo.modules.add(module); - for (const sourceSet of info.sourceSets) prefixInfo.sourceSets.add(sourceSet); - } -} - -function packageDir(pkg) { - return path.join(packagesRoot, ...pkg.split(".")); -} - -function linkToPackage(pkg, fromFile) { - const target = path.join(packageDir(pkg), "README.md"); - return path.relative(path.dirname(fromFile), target).replace(/\\/g, "/"); -} - -function directChildren(pkg, allPackages) { - const prefix = `${pkg}.`; - return allPackages - .filter((p) => p.startsWith(prefix)) - .map((p) => p.slice(prefix.length).split(".")[0]) - .filter((v, i, a) => a.indexOf(v) === i) - .map((segment) => `${prefix}${segment}`) - .filter((child) => allPackages.includes(child)); -} - -function descendants(pkg, allPackages) { - const prefix = `${pkg}.`; - return allPackages.filter((p) => p.startsWith(prefix)); -} - -function packageDescription(pkg) { - if (packageDescriptions.has(pkg)) return packageDescriptions.get(pkg); - const last = pkg.split(".").pop(); - return `Package for ${last.replace(/([a-z])([A-Z])/g, "$1 $2").toLowerCase()} related classes.`; -} - -function packageReferenceSentence(pkg) { - const info = packageMap.get(pkg); - const classCount = info ? info.classes.length : 0; - const descendantCount = info ? descendants(pkg, [...packageMap.keys()]).length : 0; - const classText = classCount === 1 ? "1 direct class" : `${classCount} direct classes`; - const descendantText = - descendantCount === 1 - ? "1 descendant package" - : `${descendantCount} descendant packages`; - const description = packageDescription(pkg) - .replace(/\s+/g, " ") - .replace(/\.\s+/g, "; ") - .replace(/\.$/, ""); - return `This reference describes ${description}, lists ${classText}, and aggregates ${descendantText}.`; -} - -function writePackageDoc(pkg, allPackages) { - const info = packageMap.get(pkg); - const outFile = path.join(packageDir(pkg), "README.md"); - fs.mkdirSync(path.dirname(outFile), { recursive: true }); - const children = directChildren(pkg, allPackages); - const desc = descendants(pkg, allPackages); - const lines = []; - lines.push(`# ${pkg}`); - lines.push(""); - lines.push(packageDescription(pkg)); - lines.push(""); - lines.push(`- Modules: ${[...info.modules].sort().join(", ") || "none directly"}`); - lines.push(`- Source sets: ${[...info.sourceSets].sort().join(", ") || "none directly"}`); - lines.push(`- Direct classes: ${info.classes.length}`); - lines.push(`- Descendant packages: ${desc.length}`); - lines.push(""); - if (info.classes.length) { - lines.push("## Classes"); - lines.push(""); - for (const cls of info.classes.sort((a, b) => a.name.localeCompare(b.name))) { - lines.push(`### ${cls.name}`); - lines.push(""); - lines.push(`- Kind: ${cls.kind}`); - lines.push(`- Source: \`${cls.source}\``); - lines.push(`- Declaration: \`${cls.declaration}\``); - lines.push(`- Responsibility: ${cls.summary}`); - lines.push(""); - } - } else { - lines.push("## Classes"); - lines.push(""); - lines.push("No direct Java classes were found in this package."); - lines.push(""); - } - if (children.length) { - lines.push("## Child Packages"); - lines.push(""); - for (const child of children.sort()) { - lines.push(`- [${child}](${linkToPackage(child, outFile)}) - ${packageReferenceSentence(child)}`); - } - lines.push(""); - } - if (desc.length) { - lines.push("## Aggregated Contents"); - lines.push(""); - lines.push(`This package aggregates ${desc.length} descendant package(s) with ${desc.reduce((n, p) => n + packageMap.get(p).classes.length, 0)} descendant class(es).`); - lines.push(""); - } - fs.writeFileSync(outFile, lines.join("\n"), "utf8"); -} - -function packageDepth(pkg) { - return pkg.split(".").length; -} - -function rootPackageList(allPackages) { - return allPackages - .filter((p) => !allPackages.some((q) => p !== q && p.startsWith(`${q}.`) && packageDepth(q) === packageDepth(p) - 1)) - .sort(); -} - -function writeRootDoc(allPackages) { - const file = path.join(root, "PROJECT_STRUCTURE.md"); - const lines = []; - lines.push("# Project Structure"); - lines.push(""); - lines.push("SpinyGUI is a modular Java GUI library with a browser-engine-like split between node tree, CSS parsing/style resolution, layout, event processing, and rendering backends."); - lines.push(""); - lines.push("Generated package documents are stored under `docs/project-structure/packages/`. Each package document lists direct classes first, then links to child packages so the documentation can be read from deepest packages upward."); - lines.push(""); - lines.push("## Gradle Modules"); - lines.push(""); - for (const [name, description] of Object.entries(moduleDescriptions)) { - lines.push(`- \`${name}\` - ${description}`); - } - lines.push(""); - lines.push("## Main Subsystems"); - lines.push(""); - const subsystems = [ - ["Node tree", "core node classes model frames, elements, text, attributes, parent/child links, pseudo-state, and box geometry."], - ["Style and CSS", "stylesheet model, selectors, property providers, ANTLR visitors, typed CSS values, and `ResolvedStyle` convert parsed CSS into values usable by layout/rendering."], - ["Layout", "layout contracts and implementations calculate box-model rectangles, text metrics, normal-flow/positioned layout trees, scroll sizes, and client sizes."], - ["Events and input", "system events are translated by system listeners/processors into application events and node state changes."], - ["Fonts and metrics", "font service/storage abstractions load platform fonts and expose text metrics for layout/rendering."], - ["Rendering", "backend SPI defines `Renderer`; the LWJGL/NanoVG backend traverses layout nodes and draws elements, borders, and text."], - ["Demos", "simple and complex demo modules exercise the aggregate API and NanoVG backend."], - ]; - for (const [name, text] of subsystems) lines.push(`- ${name}: ${text}`); - lines.push(""); - lines.push("## Package Index"); - lines.push(""); - for (const pkg of allPackages) { - lines.push(`- [${pkg}](${path.relative(root, path.join(packageDir(pkg), "README.md")).replace(/\\/g, "/")}) - ${packageReferenceSentence(pkg)}`); - } - lines.push(""); - lines.push("## Reading Order"); - lines.push(""); - lines.push("For bottom-up navigation, start with the deepest packages such as `style.stylesheet.selector.*`, `style.stylesheet.term`, `style.types.*`, `system.event.listener`, `layout.impl`, and `backend.renderer.lwjgl.nanovg.util`; then move upward through their parent package documents and finish with this root overview."); - fs.writeFileSync(file, lines.join("\n"), "utf8"); -} - -function writeIndex(allPackages) { - fs.mkdirSync(outRoot, { recursive: true }); - const file = path.join(outRoot, "README.md"); - const lines = []; - lines.push("# Project Structure Documentation"); - lines.push(""); - lines.push("This folder mirrors Java package documentation generated from the project source tree."); - lines.push(""); - lines.push("- [`PROJECT_STRUCTURE.md`](../../PROJECT_STRUCTURE.md) - This reference gives the root-level overview of Gradle modules, main subsystems, and the full package index."); - lines.push("- [`AGENTS_CODE_STYLE.md`](../../AGENTS_CODE_STYLE.md) - This reference describes the coding style, architecture principles, package conventions, and caution areas for future agents."); - lines.push(""); - lines.push("## Packages by Depth"); - lines.push(""); - for (const pkg of [...allPackages].sort((a, b) => packageDepth(b) - packageDepth(a) || a.localeCompare(b))) { - lines.push(`- [${pkg}](${path.relative(path.dirname(file), path.join(packageDir(pkg), "README.md")).replace(/\\/g, "/")}) - ${packageReferenceSentence(pkg)}`); - } - fs.writeFileSync(file, lines.join("\n"), "utf8"); -} - -if (fs.existsSync(outRoot)) { - fs.rmSync(outRoot, { recursive: true, force: true }); -} - -const allPackages = [...packageMap.keys()].sort(); -for (const pkg of [...allPackages].sort((a, b) => packageDepth(b) - packageDepth(a) || a.localeCompare(b))) { - writePackageDoc(pkg, allPackages); -} -writeIndex(allPackages); -writeRootDoc(allPackages); - -console.log(`Wrote ${allPackages.length} package docs under ${rel(outRoot)}.`); From 19f613c00c398c39c43c3d98f9ebaae97666ada0 Mon Sep 17 00:00:00 2001 From: SpinyOwl Date: Sat, 16 May 2026 22:51:36 +0200 Subject: [PATCH 007/121] Replace `@Data` with specific Lombok annotations for better clarity --- .../spinygui/core/event/ChangePositionEvent.java | 10 ++++++++-- .../spinygui/core/event/ChangeSizeEvent.java | 10 ++++++++-- .../spinygui/core/event/ChangeTextEvent.java | 10 ++++++++-- .../com/spinyowl/spinygui/core/event/CharEvent.java | 10 ++++++++-- .../spinygui/core/event/CursorEnterEvent.java | 10 ++++++++-- .../spinygui/core/event/CursorExitEvent.java | 10 ++++++++-- .../com/spinyowl/spinygui/core/event/Event.java | 10 ++++++++-- .../spinyowl/spinygui/core/event/FileDropEvent.java | 11 +++++++++-- .../spinyowl/spinygui/core/event/FocusInEvent.java | 11 +++++++++-- .../spinyowl/spinygui/core/event/FocusOutEvent.java | 11 +++++++++-- .../spinyowl/spinygui/core/event/KeyboardEvent.java | 13 ++++++++++--- .../spinygui/core/event/MouseClickEvent.java | 10 ++++++++-- .../spinygui/core/event/MouseDragEvent.java | 10 ++++++++-- .../spinyowl/spinygui/core/event/ScrollEvent.java | 10 ++++++++-- .../spinygui/core/event/WindowCloseEvent.java | 11 +++++++++-- .../spinygui/core/event/WindowFocusEvent.java | 10 ++++++++-- .../spinygui/core/event/WindowIconifyEvent.java | 10 ++++++++-- .../spinygui/core/event/WindowPosEvent.java | 10 ++++++++-- .../spinygui/core/event/WindowRefreshEvent.java | 10 ++++++++-- .../spinygui/core/event/WindowSizeEvent.java | 10 ++++++++-- .../core/parser/impl/css/antlr/CSS3Lexer.java | 6 +++--- 21 files changed, 169 insertions(+), 44 deletions(-) diff --git a/core/src/main/java/com/spinyowl/spinygui/core/event/ChangePositionEvent.java b/core/src/main/java/com/spinyowl/spinygui/core/event/ChangePositionEvent.java index 27283e5d..b663ed84 100644 --- a/core/src/main/java/com/spinyowl/spinygui/core/event/ChangePositionEvent.java +++ b/core/src/main/java/com/spinyowl/spinygui/core/event/ChangePositionEvent.java @@ -1,11 +1,17 @@ package com.spinyowl.spinygui.core.event; -import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Getter; import lombok.NonNull; +import lombok.Setter; +import lombok.ToString; import lombok.experimental.SuperBuilder; import org.joml.Vector2f; -@Data +@Getter +@Setter +@ToString +@EqualsAndHashCode @SuperBuilder public class ChangePositionEvent extends Event { @NonNull private final Vector2f oldPos; diff --git a/core/src/main/java/com/spinyowl/spinygui/core/event/ChangeSizeEvent.java b/core/src/main/java/com/spinyowl/spinygui/core/event/ChangeSizeEvent.java index da8760ef..c98d6792 100644 --- a/core/src/main/java/com/spinyowl/spinygui/core/event/ChangeSizeEvent.java +++ b/core/src/main/java/com/spinyowl/spinygui/core/event/ChangeSizeEvent.java @@ -1,11 +1,17 @@ package com.spinyowl.spinygui.core.event; -import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Getter; import lombok.NonNull; +import lombok.Setter; +import lombok.ToString; import lombok.experimental.SuperBuilder; import org.joml.Vector2f; -@Data +@Getter +@Setter +@ToString +@EqualsAndHashCode @SuperBuilder public class ChangeSizeEvent extends Event { @NonNull private final Vector2f oldSize; diff --git a/core/src/main/java/com/spinyowl/spinygui/core/event/ChangeTextEvent.java b/core/src/main/java/com/spinyowl/spinygui/core/event/ChangeTextEvent.java index 9e7557f2..f967d0db 100644 --- a/core/src/main/java/com/spinyowl/spinygui/core/event/ChangeTextEvent.java +++ b/core/src/main/java/com/spinyowl/spinygui/core/event/ChangeTextEvent.java @@ -1,10 +1,16 @@ package com.spinyowl.spinygui.core.event; -import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Getter; import lombok.NonNull; +import lombok.Setter; +import lombok.ToString; import lombok.experimental.SuperBuilder; -@Data +@Getter +@Setter +@ToString +@EqualsAndHashCode @SuperBuilder public class ChangeTextEvent extends Event { @NonNull private final String oldText; diff --git a/core/src/main/java/com/spinyowl/spinygui/core/event/CharEvent.java b/core/src/main/java/com/spinyowl/spinygui/core/event/CharEvent.java index 83e3fa77..10e84f5f 100644 --- a/core/src/main/java/com/spinyowl/spinygui/core/event/CharEvent.java +++ b/core/src/main/java/com/spinyowl/spinygui/core/event/CharEvent.java @@ -1,10 +1,16 @@ package com.spinyowl.spinygui.core.event; -import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Getter; import lombok.NonNull; +import lombok.Setter; +import lombok.ToString; import lombok.experimental.SuperBuilder; -@Data +@Getter +@Setter +@ToString +@EqualsAndHashCode @SuperBuilder public class CharEvent extends Event { @NonNull private final String input; diff --git a/core/src/main/java/com/spinyowl/spinygui/core/event/CursorEnterEvent.java b/core/src/main/java/com/spinyowl/spinygui/core/event/CursorEnterEvent.java index caae3d5f..1a963b36 100644 --- a/core/src/main/java/com/spinyowl/spinygui/core/event/CursorEnterEvent.java +++ b/core/src/main/java/com/spinyowl/spinygui/core/event/CursorEnterEvent.java @@ -1,11 +1,17 @@ package com.spinyowl.spinygui.core.event; -import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Getter; import lombok.NonNull; +import lombok.Setter; +import lombok.ToString; import lombok.experimental.SuperBuilder; import org.joml.Vector2fc; -@Data +@Getter +@Setter +@ToString +@EqualsAndHashCode @SuperBuilder public class CursorEnterEvent extends Event { @NonNull private final Vector2fc cursorPosition; diff --git a/core/src/main/java/com/spinyowl/spinygui/core/event/CursorExitEvent.java b/core/src/main/java/com/spinyowl/spinygui/core/event/CursorExitEvent.java index 5db00a3e..efedf015 100644 --- a/core/src/main/java/com/spinyowl/spinygui/core/event/CursorExitEvent.java +++ b/core/src/main/java/com/spinyowl/spinygui/core/event/CursorExitEvent.java @@ -1,11 +1,17 @@ package com.spinyowl.spinygui.core.event; -import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Getter; import lombok.NonNull; +import lombok.Setter; +import lombok.ToString; import lombok.experimental.SuperBuilder; import org.joml.Vector2fc; -@Data +@Getter +@Setter +@ToString +@EqualsAndHashCode @SuperBuilder public class CursorExitEvent extends Event { @NonNull private final Vector2fc cursorPosition; diff --git a/core/src/main/java/com/spinyowl/spinygui/core/event/Event.java b/core/src/main/java/com/spinyowl/spinygui/core/event/Event.java index 071be66b..52cd5397 100644 --- a/core/src/main/java/com/spinyowl/spinygui/core/event/Event.java +++ b/core/src/main/java/com/spinyowl/spinygui/core/event/Event.java @@ -1,10 +1,16 @@ package com.spinyowl.spinygui.core.event; -import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Getter; import lombok.NonNull; +import lombok.Setter; +import lombok.ToString; import lombok.experimental.SuperBuilder; -@Data +@Getter +@Setter +@ToString +@EqualsAndHashCode @SuperBuilder public class Event { diff --git a/core/src/main/java/com/spinyowl/spinygui/core/event/FileDropEvent.java b/core/src/main/java/com/spinyowl/spinygui/core/event/FileDropEvent.java index 584fd266..e78d00dc 100644 --- a/core/src/main/java/com/spinyowl/spinygui/core/event/FileDropEvent.java +++ b/core/src/main/java/com/spinyowl/spinygui/core/event/FileDropEvent.java @@ -1,10 +1,17 @@ package com.spinyowl.spinygui.core.event; -import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Getter; import lombok.NonNull; +import lombok.Setter; +import lombok.ToString; import lombok.experimental.SuperBuilder; -@Data + +@Getter +@Setter +@ToString +@EqualsAndHashCode @SuperBuilder public class FileDropEvent extends Event { @NonNull private final String[] paths; diff --git a/core/src/main/java/com/spinyowl/spinygui/core/event/FocusInEvent.java b/core/src/main/java/com/spinyowl/spinygui/core/event/FocusInEvent.java index ec80ed28..a3c751fe 100644 --- a/core/src/main/java/com/spinyowl/spinygui/core/event/FocusInEvent.java +++ b/core/src/main/java/com/spinyowl/spinygui/core/event/FocusInEvent.java @@ -1,11 +1,18 @@ package com.spinyowl.spinygui.core.event; import com.spinyowl.spinygui.core.node.Element; -import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Getter; import lombok.NonNull; +import lombok.Setter; +import lombok.ToString; import lombok.experimental.SuperBuilder; -@Data + +@Getter +@Setter +@ToString +@EqualsAndHashCode @SuperBuilder public class FocusInEvent extends Event { diff --git a/core/src/main/java/com/spinyowl/spinygui/core/event/FocusOutEvent.java b/core/src/main/java/com/spinyowl/spinygui/core/event/FocusOutEvent.java index 451f4819..1a0d5003 100644 --- a/core/src/main/java/com/spinyowl/spinygui/core/event/FocusOutEvent.java +++ b/core/src/main/java/com/spinyowl/spinygui/core/event/FocusOutEvent.java @@ -1,11 +1,18 @@ package com.spinyowl.spinygui.core.event; import com.spinyowl.spinygui.core.node.Element; -import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Getter; import lombok.NonNull; +import lombok.Setter; +import lombok.ToString; import lombok.experimental.SuperBuilder; -@Data + +@Getter +@Setter +@ToString +@EqualsAndHashCode @SuperBuilder public class FocusOutEvent extends Event { diff --git a/core/src/main/java/com/spinyowl/spinygui/core/event/KeyboardEvent.java b/core/src/main/java/com/spinyowl/spinygui/core/event/KeyboardEvent.java index aa76cddb..5f2afffe 100644 --- a/core/src/main/java/com/spinyowl/spinygui/core/event/KeyboardEvent.java +++ b/core/src/main/java/com/spinyowl/spinygui/core/event/KeyboardEvent.java @@ -3,12 +3,19 @@ import com.spinyowl.spinygui.core.input.KeyAction; import com.spinyowl.spinygui.core.input.KeyMod; import com.spinyowl.spinygui.core.input.KeyboardKey; -import java.util.Set; -import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Getter; import lombok.NonNull; +import lombok.Setter; +import lombok.ToString; import lombok.experimental.SuperBuilder; -@Data +import java.util.Set; + +@Getter +@Setter +@ToString +@EqualsAndHashCode @SuperBuilder public class KeyboardEvent extends Event { @NonNull private final KeyAction action; diff --git a/core/src/main/java/com/spinyowl/spinygui/core/event/MouseClickEvent.java b/core/src/main/java/com/spinyowl/spinygui/core/event/MouseClickEvent.java index 279d4824..9a750b36 100644 --- a/core/src/main/java/com/spinyowl/spinygui/core/event/MouseClickEvent.java +++ b/core/src/main/java/com/spinyowl/spinygui/core/event/MouseClickEvent.java @@ -4,12 +4,18 @@ import com.spinyowl.spinygui.core.input.KeyAction; import com.spinyowl.spinygui.core.input.KeyMod; import com.spinyowl.spinygui.core.input.MouseButton; -import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Getter; import lombok.NonNull; +import lombok.Setter; +import lombok.ToString; import lombok.experimental.SuperBuilder; import org.joml.Vector2fc; -@Data +@Getter +@Setter +@ToString +@EqualsAndHashCode @SuperBuilder public class MouseClickEvent extends Event { @NonNull private final KeyAction action; diff --git a/core/src/main/java/com/spinyowl/spinygui/core/event/MouseDragEvent.java b/core/src/main/java/com/spinyowl/spinygui/core/event/MouseDragEvent.java index a78ea3e3..0e200857 100644 --- a/core/src/main/java/com/spinyowl/spinygui/core/event/MouseDragEvent.java +++ b/core/src/main/java/com/spinyowl/spinygui/core/event/MouseDragEvent.java @@ -1,11 +1,17 @@ package com.spinyowl.spinygui.core.event; -import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Getter; import lombok.NonNull; +import lombok.Setter; +import lombok.ToString; import lombok.experimental.SuperBuilder; import org.joml.Vector2fc; -@Data +@Getter +@Setter +@ToString +@EqualsAndHashCode @SuperBuilder public class MouseDragEvent extends Event { @NonNull private final Vector2fc delta; diff --git a/core/src/main/java/com/spinyowl/spinygui/core/event/ScrollEvent.java b/core/src/main/java/com/spinyowl/spinygui/core/event/ScrollEvent.java index baf2d2a1..723ed25c 100644 --- a/core/src/main/java/com/spinyowl/spinygui/core/event/ScrollEvent.java +++ b/core/src/main/java/com/spinyowl/spinygui/core/event/ScrollEvent.java @@ -1,9 +1,15 @@ package com.spinyowl.spinygui.core.event; -import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; import lombok.experimental.SuperBuilder; -@Data +@Getter +@Setter +@ToString +@EqualsAndHashCode @SuperBuilder public class ScrollEvent extends Event { private final float offsetX; diff --git a/core/src/main/java/com/spinyowl/spinygui/core/event/WindowCloseEvent.java b/core/src/main/java/com/spinyowl/spinygui/core/event/WindowCloseEvent.java index b560ef05..2998d2cc 100644 --- a/core/src/main/java/com/spinyowl/spinygui/core/event/WindowCloseEvent.java +++ b/core/src/main/java/com/spinyowl/spinygui/core/event/WindowCloseEvent.java @@ -1,8 +1,15 @@ package com.spinyowl.spinygui.core.event; -import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; import lombok.experimental.SuperBuilder; -@Data + +@Getter +@Setter +@ToString +@EqualsAndHashCode @SuperBuilder public class WindowCloseEvent extends Event {} diff --git a/core/src/main/java/com/spinyowl/spinygui/core/event/WindowFocusEvent.java b/core/src/main/java/com/spinyowl/spinygui/core/event/WindowFocusEvent.java index 8b561ae4..c5a1b631 100644 --- a/core/src/main/java/com/spinyowl/spinygui/core/event/WindowFocusEvent.java +++ b/core/src/main/java/com/spinyowl/spinygui/core/event/WindowFocusEvent.java @@ -1,8 +1,14 @@ package com.spinyowl.spinygui.core.event; -import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; import lombok.experimental.SuperBuilder; -@Data +@Getter +@Setter +@ToString +@EqualsAndHashCode @SuperBuilder public class WindowFocusEvent extends Event {} diff --git a/core/src/main/java/com/spinyowl/spinygui/core/event/WindowIconifyEvent.java b/core/src/main/java/com/spinyowl/spinygui/core/event/WindowIconifyEvent.java index f842e748..6a1f35f9 100644 --- a/core/src/main/java/com/spinyowl/spinygui/core/event/WindowIconifyEvent.java +++ b/core/src/main/java/com/spinyowl/spinygui/core/event/WindowIconifyEvent.java @@ -1,9 +1,15 @@ package com.spinyowl.spinygui.core.event; -import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; import lombok.experimental.SuperBuilder; -@Data +@Getter +@Setter +@ToString +@EqualsAndHashCode @SuperBuilder public class WindowIconifyEvent extends Event { private final boolean iconified; diff --git a/core/src/main/java/com/spinyowl/spinygui/core/event/WindowPosEvent.java b/core/src/main/java/com/spinyowl/spinygui/core/event/WindowPosEvent.java index 3e5509ef..7003c85b 100644 --- a/core/src/main/java/com/spinyowl/spinygui/core/event/WindowPosEvent.java +++ b/core/src/main/java/com/spinyowl/spinygui/core/event/WindowPosEvent.java @@ -1,9 +1,15 @@ package com.spinyowl.spinygui.core.event; -import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; import lombok.experimental.SuperBuilder; -@Data +@Getter +@Setter +@ToString +@EqualsAndHashCode @SuperBuilder public class WindowPosEvent extends Event { private final int posX; diff --git a/core/src/main/java/com/spinyowl/spinygui/core/event/WindowRefreshEvent.java b/core/src/main/java/com/spinyowl/spinygui/core/event/WindowRefreshEvent.java index 9160f282..f953bc81 100644 --- a/core/src/main/java/com/spinyowl/spinygui/core/event/WindowRefreshEvent.java +++ b/core/src/main/java/com/spinyowl/spinygui/core/event/WindowRefreshEvent.java @@ -1,8 +1,14 @@ package com.spinyowl.spinygui.core.event; -import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; import lombok.experimental.SuperBuilder; -@Data +@Getter +@Setter +@ToString +@EqualsAndHashCode @SuperBuilder public class WindowRefreshEvent extends Event {} diff --git a/core/src/main/java/com/spinyowl/spinygui/core/event/WindowSizeEvent.java b/core/src/main/java/com/spinyowl/spinygui/core/event/WindowSizeEvent.java index cc2340a5..65788bc1 100644 --- a/core/src/main/java/com/spinyowl/spinygui/core/event/WindowSizeEvent.java +++ b/core/src/main/java/com/spinyowl/spinygui/core/event/WindowSizeEvent.java @@ -1,9 +1,15 @@ package com.spinyowl.spinygui.core.event; -import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; import lombok.experimental.SuperBuilder; -@Data +@Getter +@Setter +@ToString +@EqualsAndHashCode @SuperBuilder public class WindowSizeEvent extends Event { private final int width; diff --git a/core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3Lexer.java b/core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3Lexer.java index 8a0ed0d1..fb2b2556 100644 --- a/core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3Lexer.java +++ b/core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3Lexer.java @@ -14,9 +14,9 @@ @SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast", "CheckReturnValue", "this-escape"}) public class CSS3Lexer extends Lexer { - static { - RuntimeMetaData.checkVersion("4.13.2", RuntimeMetaData.VERSION); - } + static { + RuntimeMetaData.checkVersion("4.13.2", RuntimeMetaData.VERSION); + } protected static final DFA[] _decisionToDFA; protected static final PredictionContextCache _sharedContextCache = From ad4e9b4494f746c4acce296b3e62817258a25422 Mon Sep 17 00:00:00 2001 From: SpinyOwl Date: Sun, 17 May 2026 17:21:21 +0200 Subject: [PATCH 008/121] Add additional Maven snapshot repository in build conventions --- .../main/kotlin/buildlogic.java-common-conventions.gradle.kts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/buildSrc/src/main/kotlin/buildlogic.java-common-conventions.gradle.kts b/buildSrc/src/main/kotlin/buildlogic.java-common-conventions.gradle.kts index 76cc6c0f..ac6e39b6 100644 --- a/buildSrc/src/main/kotlin/buildlogic.java-common-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/buildlogic.java-common-conventions.gradle.kts @@ -8,7 +8,9 @@ plugins { repositories { mavenCentral() maven { url = uri("https://oss.sonatype.org/content/repositories/snapshots/") } + maven { url = uri("https://central.sonatype.com/repository/maven-snapshots") } maven { url = uri("https://raw.githubusercontent.com/SpinyOwl/repo/releases") } + } dependencies { From 3efa9a1c83bee394c2742d8f076866af1063283f Mon Sep 17 00:00:00 2001 From: SpinyOwl Date: Sun, 17 May 2026 17:21:31 +0200 Subject: [PATCH 009/121] Align dependency definitions and formatting in `libs.versions.toml` for improved readability --- gradle/libs.versions.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8e4b74b5..2fce2b90 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -15,13 +15,14 @@ junit = "6.0.3" junitPlatform = "1.10.5" logback = "1.5.32" lombok = "1.18.46" -lwjgl = "3.4.1" +lwjgl = "3.4.2-SNAPSHOT" mockito = "5.23.0" netty = "4.1.120.Final" slf4j = "2.0.18" snakeYaml = "2.6" + [libraries] antlr = { module = "org.antlr:antlr4", version.ref = "antlr" } antlrRuntime = { module = "org.antlr:antlr4-runtime", version.ref = "antlr" } @@ -52,7 +53,6 @@ netty = { module = "io.netty:netty-all", version.ref = "netty" } slf4j = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" } snakeYaml = { module = "org.yaml:snakeyaml", version.ref = "snakeYaml" } - [bundles] lwjgl = [ "lwjgl", From b7d4ca0b7ff04668325b4addf00073fd328801bf Mon Sep 17 00:00:00 2001 From: SpinyOwl Date: Sun, 17 May 2026 17:21:43 +0200 Subject: [PATCH 010/121] Set JVM arguments for `demo.simple` and `demo.complex` modules, update main class in `demo.complex` --- demo.complex/build.gradle.kts | 15 ++++++++++++++- demo.simple/build.gradle.kts | 13 +++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/demo.complex/build.gradle.kts b/demo.complex/build.gradle.kts index a01ddf7a..13ddbacc 100644 --- a/demo.complex/build.gradle.kts +++ b/demo.complex/build.gradle.kts @@ -26,5 +26,18 @@ dependencies { } application { - mainClass.set("com.spinyowl.spinygui.demo.complex.Main") + mainClass.set("com.spinyowl.spinygui.demo.complex.NvgExample") + applicationDefaultJvmArgs = listOf( + "--add-opens", "java.base/sun.misc=ALL-UNNAMED", + "--add-opens", "com.spinyowl.cbchain/com.spinyowl.cbchain=org.lwjgl", + "--add-reads", "org.lwjgl=com.spinyowl.cbchain", "--enable-native-access=ALL-UNNAMED" + ) +} + +tasks.named("run") { + jvmArgs = listOf( + "--add-opens", "java.base/sun.misc=ALL-UNNAMED", + "--add-opens", "com.spinyowl.cbchain/com.spinyowl.cbchain=org.lwjgl", + "--add-reads", "org.lwjgl=com.spinyowl.cbchain" + ) } diff --git a/demo.simple/build.gradle.kts b/demo.simple/build.gradle.kts index 68d58770..d7e15dad 100644 --- a/demo.simple/build.gradle.kts +++ b/demo.simple/build.gradle.kts @@ -8,4 +8,17 @@ dependencies { application { mainClass.set("com.spinyowl.spinygui.demo.simple.Main") + applicationDefaultJvmArgs = listOf( + "--add-opens", "java.base/sun.misc=ALL-UNNAMED", + "--add-opens", "com.spinyowl.cbchain/com.spinyowl.cbchain=org.lwjgl", + "--add-reads", "org.lwjgl=com.spinyowl.cbchain" + ) +} + +tasks.named("run") { + jvmArgs = listOf( + "--add-opens", "java.base/sun.misc=ALL-UNNAMED", + "--add-opens", "com.spinyowl.cbchain/com.spinyowl.cbchain=org.lwjgl", + "--add-reads", "org.lwjgl=com.spinyowl.cbchain" + ) } From 0b600832895a0e8b6eb2246989fb163796b52686 Mon Sep 17 00:00:00 2001 From: SpinyOwl Date: Sun, 17 May 2026 17:27:47 +0200 Subject: [PATCH 011/121] Implement inline box fragments and improve inline layout handling --- .../lwjgl/nanovg/NvgBorderRenderer.java | 29 ++ .../lwjgl/nanovg/NvgElementRenderer.java | 27 + .../lwjgl/nanovg/NvgTextRenderer.java | 20 +- .../src/main/java/module-info.java | 4 +- .../spinygui/core/layout/InlineFragment.java | 2 + .../layout/impl/InlineFormattingContext.java | 187 +++++-- .../spinyowl/spinygui/core/node/Element.java | 15 +- .../core/parser/impl/css/antlr/CSS3Lexer.java | 6 +- .../parser/impl/css/antlr/CSS3Parser.java | 488 ++++++++++++++---- .../core/layout/impl/BlockLayoutTest.java | 83 +++ .../impl/InlineFormattingContextTest.java | 71 ++- 11 files changed, 784 insertions(+), 148 deletions(-) diff --git a/core.backend.lwjgl.nanovg/src/main/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/NvgBorderRenderer.java b/core.backend.lwjgl.nanovg/src/main/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/NvgBorderRenderer.java index 4a9cc5cf..e3506cef 100644 --- a/core.backend.lwjgl.nanovg/src/main/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/NvgBorderRenderer.java +++ b/core.backend.lwjgl.nanovg/src/main/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/NvgBorderRenderer.java @@ -6,6 +6,7 @@ import com.spinyowl.spinygui.core.node.Element; import com.spinyowl.spinygui.core.node.Node; +import com.spinyowl.spinygui.core.style.types.Display; import com.spinyowl.spinygui.core.style.types.border.BorderStyle; import org.joml.Vector2f; @@ -17,7 +18,27 @@ public void render(Node node, long nanovg) { createScissor(nanovg, node); var style = element.resolvedStyle(); if (BorderStyle.NONE.equals(style.borderTopStyle())) return; + float borderThickness = element.box().border().top(); + if (Display.INLINE.equals(style.display()) && !element.inlineFragments().isEmpty()) { + Vector2f offset = inlineFormattingOffset(element); + element + .inlineFragments() + .forEach( + fragment -> + drawRectStroke( + nanovg, + new Vector2f( + offset.x + fragment.x() + borderThickness / 2, + offset.y + fragment.y() + borderThickness / 2), + new Vector2f( + Math.max(0, fragment.width() - borderThickness), + Math.max(0, fragment.height() - borderThickness)), + style.borderTopColor(), + borderThickness)); + resetScissor(nanovg); + return; + } Vector2f position = element.absolutePosition().add(borderThickness / 2, borderThickness / 2); Vector2f size = element.size().sub(borderThickness, borderThickness); @@ -25,4 +46,12 @@ public void render(Node node, long nanovg) { drawRectStroke(nanovg, position, size, style.borderTopColor(), borderThickness); resetScissor(nanovg); } + + private Vector2f inlineFormattingOffset(Element element) { + Element parent = element.parent(); + while (parent != null && Display.INLINE.equals(parent.resolvedStyle().display())) { + parent = parent.parent(); + } + return parent == null ? new Vector2f() : parent.absolutePosition(); + } } diff --git a/core.backend.lwjgl.nanovg/src/main/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/NvgElementRenderer.java b/core.backend.lwjgl.nanovg/src/main/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/NvgElementRenderer.java index ce106613..c4788d24 100644 --- a/core.backend.lwjgl.nanovg/src/main/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/NvgElementRenderer.java +++ b/core.backend.lwjgl.nanovg/src/main/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/NvgElementRenderer.java @@ -10,6 +10,8 @@ import com.spinyowl.spinygui.core.node.Element; import com.spinyowl.spinygui.core.node.Node; +import com.spinyowl.spinygui.core.style.types.Display; +import org.joml.Vector2f; public class NvgElementRenderer { @@ -17,6 +19,23 @@ public void render(Node node, long nanovg) { Element element = node.asElement(); if (visible(element) /*&& visibleInParents(element)*/) { var style = element.resolvedStyle(); + if (Display.INLINE.equals(style.display()) && !element.inlineFragments().isEmpty()) { + createScissor(nanovg, node); + nvgSave(nanovg); + Vector2f offset = inlineFormattingOffset(element); + element + .inlineFragments() + .forEach( + fragment -> + drawRect( + nanovg, + new Vector2f(offset.x + fragment.x(), offset.y + fragment.y()), + new Vector2f(fragment.width(), fragment.height()), + style.backgroundColor())); + nvgRestore(nanovg); + resetScissor(nanovg); + return; + } var backgroundColor = style.backgroundColor(); var borderRadius = getBorderRadius(element, style); @@ -31,4 +50,12 @@ public void render(Node node, long nanovg) { resetScissor(nanovg); } } + + private Vector2f inlineFormattingOffset(Element element) { + Element parent = element.parent(); + while (parent != null && Display.INLINE.equals(parent.resolvedStyle().display())) { + parent = parent.parent(); + } + return parent == null ? new Vector2f() : parent.absolutePosition(); + } } diff --git a/core.backend.lwjgl.nanovg/src/main/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/NvgTextRenderer.java b/core.backend.lwjgl.nanovg/src/main/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/NvgTextRenderer.java index 424d7b25..8c98218f 100644 --- a/core.backend.lwjgl.nanovg/src/main/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/NvgTextRenderer.java +++ b/core.backend.lwjgl.nanovg/src/main/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/NvgTextRenderer.java @@ -1,8 +1,8 @@ package com.spinyowl.spinygui.core.backend.renderer.lwjgl.nanovg; +import static com.spinyowl.spinygui.core.backend.renderer.lwjgl.nanovg.util.NvgColorUtil.create; import static com.spinyowl.spinygui.core.backend.renderer.lwjgl.nanovg.util.NvgRenderUtils.createScissor; import static com.spinyowl.spinygui.core.backend.renderer.lwjgl.nanovg.util.NvgRenderUtils.resetScissor; -import static com.spinyowl.spinygui.core.backend.renderer.lwjgl.nanovg.util.NvgColorUtil.create; import static org.lwjgl.nanovg.NanoVG.NVG_ALIGN_BASELINE; import static org.lwjgl.nanovg.NanoVG.NVG_ALIGN_LEFT; import static org.lwjgl.nanovg.NanoVG.nvgCreateFontMem; @@ -16,10 +16,12 @@ import static org.lwjgl.system.MemoryUtil.memFree; import static org.lwjgl.system.MemoryUtil.memUTF8; +import com.spinyowl.spinygui.core.font.Font; import com.spinyowl.spinygui.core.layout.InlineFragment; +import com.spinyowl.spinygui.core.node.Element; import com.spinyowl.spinygui.core.node.Node; import com.spinyowl.spinygui.core.node.Text; -import com.spinyowl.spinygui.core.font.Font; +import com.spinyowl.spinygui.core.style.types.Display; import com.spinyowl.spinygui.core.util.IOUtil; import java.nio.ByteBuffer; import java.util.HashMap; @@ -40,8 +42,7 @@ public void render(Node node, long nanovg) { nvgSave(nanovg); nvgTextAlign(nanovg, NVG_ALIGN_LEFT | NVG_ALIGN_BASELINE); - Vector2f offset = - text.offsetParent() == null ? new Vector2f() : text.offsetParent().absolutePosition(); + Vector2f offset = inlineFormattingOffset(text); for (InlineFragment fragment : text.inlineFragments()) { renderFragment(fragment, nanovg, offset); } @@ -50,6 +51,17 @@ public void render(Node node, long nanovg) { resetScissor(nanovg); } + private Vector2f inlineFormattingOffset(Text text) { + Element parent = text.parent(); + while (parent != null && Display.INLINE.equals(parent.resolvedStyle().display())) { + parent = parent.parent(); + } + if (parent != null) { + return parent.absolutePosition(); + } + return text.offsetParent() == null ? new Vector2f() : text.offsetParent().absolutePosition(); + } + private void renderFragment(InlineFragment fragment, long nanovg, Vector2f offset) { if (!fragment.textFragment()) { return; diff --git a/core.backend.lwjgl.nanovg/src/main/java/module-info.java b/core.backend.lwjgl.nanovg/src/main/java/module-info.java index b2c37fe0..a963dc6f 100644 --- a/core.backend.lwjgl.nanovg/src/main/java/module-info.java +++ b/core.backend.lwjgl.nanovg/src/main/java/module-info.java @@ -1,9 +1,7 @@ open module com.spinyowl.spinygui.core.backend.lwjgl.nanovg { requires com.spinyowl.spinygui.core; requires com.spinyowl.spinygui.core.backend; - - requires lombok; - + requires static lombok; requires org.lwjgl; requires org.lwjgl.natives; requires org.lwjgl.glfw; diff --git a/core/src/main/java/com/spinyowl/spinygui/core/layout/InlineFragment.java b/core/src/main/java/com/spinyowl/spinygui/core/layout/InlineFragment.java index 6d7cca05..921a9395 100644 --- a/core/src/main/java/com/spinyowl/spinygui/core/layout/InlineFragment.java +++ b/core/src/main/java/com/spinyowl/spinygui/core/layout/InlineFragment.java @@ -4,10 +4,12 @@ import com.spinyowl.spinygui.core.node.Node; import com.spinyowl.spinygui.core.style.types.Color; import lombok.Builder; +import lombok.EqualsAndHashCode; import lombok.Value; @Value @Builder +@EqualsAndHashCode(exclude = "node") public class InlineFragment { Node node; String text; diff --git a/core/src/main/java/com/spinyowl/spinygui/core/layout/impl/InlineFormattingContext.java b/core/src/main/java/com/spinyowl/spinygui/core/layout/impl/InlineFormattingContext.java index f8ec4089..3a0829b5 100644 --- a/core/src/main/java/com/spinyowl/spinygui/core/layout/impl/InlineFormattingContext.java +++ b/core/src/main/java/com/spinyowl/spinygui/core/layout/impl/InlineFormattingContext.java @@ -4,12 +4,12 @@ import static com.spinyowl.spinygui.core.layout.impl.LayoutUtils.setPadding; import static com.spinyowl.spinygui.core.style.stylesheet.util.StyleUtils.getFloatLengthOptional; import static com.spinyowl.spinygui.core.style.types.Display.INLINE; -import static com.spinyowl.spinygui.core.style.types.OverflowWrap.BREAK_WORD; import static com.spinyowl.spinygui.core.style.types.OverflowWrap.ANYWHERE; +import static com.spinyowl.spinygui.core.style.types.OverflowWrap.BREAK_WORD; import static com.spinyowl.spinygui.core.style.types.TextAlign.CENTER; import static com.spinyowl.spinygui.core.style.types.TextAlign.RIGHT; -import static com.spinyowl.spinygui.core.style.types.WhiteSpace.NOWRAP; import static com.spinyowl.spinygui.core.style.types.WhiteSpace.NORMAL; +import static com.spinyowl.spinygui.core.style.types.WhiteSpace.NOWRAP; import static com.spinyowl.spinygui.core.style.types.WhiteSpace.PRE; import static com.spinyowl.spinygui.core.style.types.WhiteSpace.PRE_LINE; import static com.spinyowl.spinygui.core.style.types.WhiteSpace.PRE_WRAP; @@ -31,7 +31,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; -import java.util.HashMap; +import java.util.IdentityHashMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -52,8 +52,8 @@ public float layout(@NonNull Element parent, @NonNull List nodes, float st float contentY = parent.box().border().top() + parent.box().padding().top() + startY; float availableWidth = Math.max(0, parent.box().content().width()); List units = new ArrayList<>(); - Map> textFragments = new HashMap<>(); - Map> elementFragments = new HashMap<>(); + Map> textFragments = new IdentityHashMap<>(); + Map> elementFragments = new IdentityHashMap<>(); nodes.forEach(node -> collectUnits(node, units, textFragments, elementFragments, parent)); List lines = buildLines(parent, units, contentX, contentY, availableWidth); @@ -63,12 +63,12 @@ public float layout(@NonNull Element parent, @NonNull List nodes, float st for (InlineFragment fragment : line.fragments()) { if (fragment.node() instanceof Text text) { textFragments.computeIfAbsent(text, k -> new ArrayList<>()).add(fragment); - } else if (fragment.node() instanceof Element element) { - elementFragments.computeIfAbsent(element, k -> new ArrayList<>()).add(fragment); } } } + collectElementLineFragments(lines, elementFragments); + textFragments.forEach( (text, fragments) -> { text.inlineFragments(fragments); @@ -85,10 +85,7 @@ public float layout(@NonNull Element parent, @NonNull List nodes, float st for (Node node : nodes) { if (node instanceof Element element) { - List fragments = new ArrayList<>(); - collectElementFragments(element, textFragments, fragments); - fragments.addAll(elementFragments.getOrDefault(element, List.of())); - applyUnionBox(element, fragments); + applyInlineElementFragments(element, textFragments, elementFragments); } } @@ -121,6 +118,7 @@ private void collectUnits( } ResolvedStyle style = element.resolvedStyle(); + element.inlineFragments(List.of()); setBorders(style, element.box().border()); setPadding( inheritedParent.box().content().width(), @@ -129,10 +127,14 @@ private void collectUnits( element.box().padding()); setMargins(element, inheritedParent.box().content().width()); - float left = element.box().margin().left() + element.box().border().left() + element.box().padding().left(); - if (left > 0) { - units.add(new InlineUnit(element, style, null, false, false, true, left)); - } + addSpacer(units, element, style, element.box().margin().left(), false); + addSpacer( + units, + element, + style, + element.box().border().left() + element.box().padding().left(), + true); + addVerticalStrut(units, element, style); for (Node child : element.childNodes()) { if (inlineNode(child)) { @@ -140,10 +142,35 @@ private void collectUnits( } } - float right = - element.box().padding().right() + element.box().border().right() + element.box().margin().right(); - if (right > 0) { - units.add(new InlineUnit(element, style, null, false, false, true, right)); + addSpacer( + units, + element, + style, + element.box().padding().right() + element.box().border().right(), + true); + addSpacer(units, element, style, element.box().margin().right(), false); + } + + private void addSpacer( + List units, + Element element, + ResolvedStyle style, + float width, + boolean elementBox) { + if (width > 0) { + units.add(new InlineUnit(element, style, null, false, false, true, width, 0, elementBox)); + } + } + + private void addVerticalStrut(List units, Element element, ResolvedStyle style) { + float verticalAdditions = + element.box().border().top() + + element.box().padding().top() + + element.box().padding().bottom() + + element.box().border().bottom(); + if (verticalAdditions > 0) { + units.add( + new InlineUnit(element, style, null, false, false, true, 0, verticalAdditions, true)); } } @@ -157,11 +184,11 @@ private List textUnits(Text text, ResolvedStyle style) { char c = normalized.charAt(i); if (c == '\n') { addTextUnit(text, style, normalized.substring(start, i), preserveSpaces, result); - result.add(new InlineUnit(text, style, null, false, true, false, 0)); + result.add(new InlineUnit(text, style, null, false, true, false, 0, 0, false)); start = i + 1; } else if (!preserveSpaces && c == ' ') { addTextUnit(text, style, normalized.substring(start, i), false, result); - result.add(new InlineUnit(text, style, " ", true, false, false, 0)); + result.add(new InlineUnit(text, style, " ", true, false, false, 0, 0, false)); start = i + 1; } } @@ -176,10 +203,12 @@ private void addTextUnit( } if (preserveSpaces) { for (int i = 0; i < value.length(); i++) { - units.add(new InlineUnit(text, style, String.valueOf(value.charAt(i)), false, false, false, 0)); + units.add( + new InlineUnit( + text, style, String.valueOf(value.charAt(i)), false, false, false, 0, 0, false)); } } else { - units.add(new InlineUnit(text, style, value, false, false, false, 0)); + units.add(new InlineUnit(text, style, value, false, false, false, 0, 0, false)); } } @@ -263,7 +292,7 @@ private float addUnit(LineBox line, InlineUnit unit, float cursorX) { float baseline = line.y() + unit.baseline(textMeasurer); line.addFragment( InlineFragment.builder() - .node(unit.node) + .node(unit.fragmentNode()) .text(unit.text) .x(cursorX) .y(line.y()) @@ -350,17 +379,75 @@ private void applyUnionBox(Node node, List fragments) { node.box().contentSize(maxX - minX, maxY - minY); } - private void collectElementFragments( - Element element, Map> textFragments, List fragments) { + private void applyInlineElementFragments( + Element element, + Map> textFragments, + Map> elementFragments) { + List fragments = new ArrayList<>(); + collectDescendantTextFragments(element, textFragments, fragments); + fragments.addAll(elementFragments.getOrDefault(element, List.of())); + element.inlineFragments(elementFragments.getOrDefault(element, List.of())); + applyUnionBox(element, fragments); + element.children().stream() + .filter(child -> INLINE.equals(child.resolvedStyle().display())) + .forEach(child -> applyInlineElementFragments(child, textFragments, elementFragments)); + } + + private void collectDescendantTextFragments( + Element element, + Map> textFragments, + List fragments) { for (Node child : element.childNodes()) { if (child instanceof Text text) { fragments.addAll(textFragments.getOrDefault(text, List.of())); } else if (child instanceof Element childElement) { - collectElementFragments(childElement, textFragments, fragments); + collectDescendantTextFragments(childElement, textFragments, fragments); + } + } + } + + private void collectElementLineFragments( + List lines, Map> elementFragments) { + for (LineBox line : lines) { + Map bounds = new IdentityHashMap<>(); + for (InlineFragment fragment : line.fragments()) { + if (fragment.node() instanceof Text text) { + collectTextAncestors(text, fragment, bounds); + } else if (fragment.node() instanceof Element element && fragment.width() > 0) { + addElementBounds(element, fragment, bounds); + } } + bounds.forEach( + (element, fragmentBounds) -> + elementFragments + .computeIfAbsent(element, ignored -> new ArrayList<>()) + .add(fragmentBounds.toFragment(element))); } } + private void collectTextAncestors( + Text text, InlineFragment fragment, Map bounds) { + Element current = text.parent(); + while (current != null && INLINE.equals(current.resolvedStyle().display())) { + addElementBounds(current, fragment, bounds); + current = current.parent(); + } + } + + private void addElementBounds( + Element element, InlineFragment fragment, Map bounds) { + FragmentBounds fragmentBounds = + bounds.computeIfAbsent(element, ignored -> new FragmentBounds()); + fragmentBounds.include(fragment, verticalExpansion(element)); + } + + private float verticalExpansion(Element element) { + return element.box().border().top() + + element.box().padding().top() + + element.box().padding().bottom() + + element.box().border().bottom(); + } + private Font findFont(ResolvedStyle style) { Set fontFamilies = style.fontFamilies(); FontStyle fontStyle = style.fontStyle(); @@ -389,6 +476,8 @@ private class InlineUnit { private final boolean newline; private final boolean spacer; private final float spacerWidth; + private final float extraHeight; + private final boolean elementBox; private final Font font; private final float fontSize; private TextLineMetrics measurement; @@ -400,7 +489,9 @@ private InlineUnit( boolean space, boolean newline, boolean spacer, - float spacerWidth) { + float spacerWidth, + float extraHeight, + boolean elementBox) { this.node = node; this.style = style; this.text = text; @@ -408,6 +499,8 @@ private InlineUnit( this.newline = newline; this.spacer = spacer; this.spacerWidth = spacerWidth; + this.extraHeight = extraHeight; + this.elementBox = elementBox; this.font = findFont(style); Float measuredFontSize = StyleUtils.getFontSize(node); this.fontSize = measuredFontSize == null ? 16f : measuredFontSize; @@ -428,11 +521,11 @@ TextLineMetrics measurement(TextMeasurer measurer) { } float lineHeight(TextMeasurer measurer) { - return measurement(measurer).height(); + return measurement(measurer).height() + extraHeight; } float baseline(TextMeasurer measurer) { - return measurement(measurer).baseline(); + return measurement(measurer).baseline() + extraHeight / 2; } boolean wraps() { @@ -451,9 +544,41 @@ boolean breakWord(TextMeasurer measurer, float availableWidth) { List breakIntoCharacters() { var parts = new ArrayList(); for (int i = 0; i < text.length(); i++) { - parts.add(new InlineUnit(node, style, String.valueOf(text.charAt(i)), false, false, false, 0)); + parts.add( + new InlineUnit( + node, style, String.valueOf(text.charAt(i)), false, false, false, 0, 0, false)); } return parts; } + + Node fragmentNode() { + return spacer && !elementBox ? null : node; + } + } + + private static class FragmentBounds { + private float minX = Float.MAX_VALUE; + private float minY = Float.MAX_VALUE; + private float maxX = -Float.MAX_VALUE; + private float maxY = -Float.MAX_VALUE; + + void include(InlineFragment fragment, float verticalExpansion) { + float yExpansion = verticalExpansion / 2; + minX = Math.min(minX, fragment.x()); + minY = Math.min(minY, fragment.y() - yExpansion); + maxX = Math.max(maxX, fragment.x() + fragment.width()); + maxY = Math.max(maxY, fragment.y() + fragment.height() + yExpansion); + } + + InlineFragment toFragment(Element element) { + return InlineFragment.builder() + .node(element) + .x(minX) + .y(minY) + .width(maxX - minX) + .height(maxY - minY) + .color(element.resolvedStyle().color()) + .build(); + } } } diff --git a/core/src/main/java/com/spinyowl/spinygui/core/node/Element.java b/core/src/main/java/com/spinyowl/spinygui/core/node/Element.java index 1a6bbbef..76466b62 100644 --- a/core/src/main/java/com/spinyowl/spinygui/core/node/Element.java +++ b/core/src/main/java/com/spinyowl/spinygui/core/node/Element.java @@ -5,7 +5,9 @@ import com.spinyowl.spinygui.core.event.Event; import com.spinyowl.spinygui.core.event.EventTarget; import com.spinyowl.spinygui.core.event.listener.EventListener; +import com.spinyowl.spinygui.core.layout.InlineFragment; import com.spinyowl.spinygui.core.style.ResolvedStyle; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; @@ -20,7 +22,7 @@ @Getter @Setter -@ToString(exclude = {"childNodes", "resolvedStyle", "listenerMap"}) +@ToString(exclude = {"childNodes", "resolvedStyle", "listenerMap", "inlineFragments"}) public class Element extends Node implements EventTarget { /** The number of pixels that an element's content is scrolled vertically. */ @@ -51,6 +53,8 @@ public class Element extends Node implements EventTarget { /** Child nodes. */ private final List childNodes = new LinkedList<>(); + private final List inlineFragments = new ArrayList<>(); + @Getter(AccessLevel.NONE) @Setter(AccessLevel.NONE) private Node firstChild; @@ -190,6 +194,15 @@ public boolean hasChildNodes() { return !childNodes.isEmpty(); } + public List inlineFragments() { + return Collections.unmodifiableList(inlineFragments); + } + + public void inlineFragments(List fragments) { + inlineFragments.clear(); + inlineFragments.addAll(fragments); + } + public Element nextElementSibling() { Node next = nextSibling(); while (next != null && !(next instanceof Element)) { diff --git a/core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3Lexer.java b/core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3Lexer.java index fb2b2556..def67903 100644 --- a/core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3Lexer.java +++ b/core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3Lexer.java @@ -14,9 +14,9 @@ @SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast", "CheckReturnValue", "this-escape"}) public class CSS3Lexer extends Lexer { - static { - RuntimeMetaData.checkVersion("4.13.2", RuntimeMetaData.VERSION); - } + static { + RuntimeMetaData.checkVersion("4.13.2", RuntimeMetaData.VERSION); + } protected static final DFA[] _decisionToDFA; protected static final PredictionContextCache _sharedContextCache = diff --git a/core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3Parser.java b/core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3Parser.java index 3c0fadf2..264d79d4 100644 --- a/core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3Parser.java +++ b/core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3Parser.java @@ -1,6 +1,7 @@ // Generated from com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3.g4 by ANTLR 4.13.2 package com.spinyowl.spinygui.core.parser.impl.css.antlr; +import java.util.List; import org.antlr.v4.runtime.NoViableAltException; import org.antlr.v4.runtime.Parser; import org.antlr.v4.runtime.ParserRuleContext; @@ -19,97 +20,374 @@ import org.antlr.v4.runtime.tree.ParseTreeVisitor; import org.antlr.v4.runtime.tree.TerminalNode; -import java.util.List; - @SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast", "CheckReturnValue", "this-escape"}) public class CSS3Parser extends Parser { - static { - RuntimeMetaData.checkVersion("4.13.2", RuntimeMetaData.VERSION); - } + static { + RuntimeMetaData.checkVersion("4.13.2", RuntimeMetaData.VERSION); + } protected static final DFA[] _decisionToDFA; protected static final PredictionContextCache _sharedContextCache = new PredictionContextCache(); - public static final int - T__0=1, T__1=2, T__2=3, T__3=4, T__4=5, T__5=6, T__6=7, T__7=8, T__8=9, - T__9=10, T__10=11, T__11=12, T__12=13, T__13=14, T__14=15, Comment=16, - Space=17, Cdo=18, Cdc=19, Includes=20, DashMatch=21, Hash=22, Import=23, - Page=24, Media=25, Namespace=26, Charset=27, Important=28, Percentage=29, - Uri=30, UnicodeRange=31, MediaOnly=32, Not=33, And=34, Dimension=35, UnknownDimension=36, - Plus=37, Minus=38, Greater=39, Comma=40, Tilde=41, PseudoNot=42, Number=43, - String_=44, PrefixMatch=45, SuffixMatch=46, SubstringMatch=47, FontFace=48, - Supports=49, Or=50, Keyframes=51, From=52, To=53, Calc=54, Viewport=55, - CounterStyle=56, FontFeatureValues=57, DxImageTransform=58, Variable=59, - Var=60, Ident=61, Function_=62; - public static final int - RULE_stylesheet = 0, RULE_charset = 1, RULE_imports = 2, RULE_namespace_ = 3, - RULE_namespacePrefix = 4, RULE_media = 5, RULE_mediaQueryList = 6, RULE_mediaQuery = 7, - RULE_mediaType = 8, RULE_mediaExpression = 9, RULE_mediaFeature = 10, - RULE_page = 11, RULE_pseudoPage = 12, RULE_selectorGroup = 13, RULE_selector = 14, - RULE_combinator = 15, RULE_simpleSelectorSequence = 16, RULE_typeSelector = 17, - RULE_typeNamespacePrefix = 18, RULE_elementName = 19, RULE_universal = 20, - RULE_className = 21, RULE_attrib = 22, RULE_pseudo = 23, RULE_functionalPseudo = 24, - RULE_expression = 25, RULE_negation = 26, RULE_negationArg = 27, RULE_operator_ = 28, - RULE_property_ = 29, RULE_ruleset = 30, RULE_declarationList = 31, RULE_declaration = 32, - RULE_prio = 33, RULE_value = 34, RULE_expr = 35, RULE_term = 36, RULE_function_ = 37, - RULE_dxImageTransform = 38, RULE_hexcolor = 39, RULE_number = 40, RULE_percentage = 41, - RULE_dimension = 42, RULE_unknownDimension = 43, RULE_any_ = 44, RULE_atRule = 45, - RULE_atKeyword = 46, RULE_unused = 47, RULE_block = 48, RULE_nestedStatement = 49, - RULE_groupRuleBody = 50, RULE_supportsRule = 51, RULE_supportsCondition = 52, - RULE_supportsConditionInParens = 53, RULE_supportsNegation = 54, RULE_supportsConjunction = 55, - RULE_supportsDisjunction = 56, RULE_supportsDeclarationCondition = 57, - RULE_generalEnclosed = 58, RULE_var_ = 59, RULE_calc = 60, RULE_calcSum = 61, - RULE_calcProduct = 62, RULE_calcValue = 63, RULE_fontFaceRule = 64, RULE_fontFaceDeclaration = 65, - RULE_keyframesRule = 66, RULE_keyframesBlocks = 67, RULE_keyframeSelector = 68, - RULE_viewport = 69, RULE_counterStyle = 70, RULE_fontFeatureValuesRule = 71, - RULE_fontFamilyNameList = 72, RULE_fontFamilyName = 73, RULE_featureValueBlock = 74, - RULE_featureType = 75, RULE_featureValueDefinition = 76, RULE_ident = 77, - RULE_ws = 78; + public static final int T__0 = 1, + T__1 = 2, + T__2 = 3, + T__3 = 4, + T__4 = 5, + T__5 = 6, + T__6 = 7, + T__7 = 8, + T__8 = 9, + T__9 = 10, + T__10 = 11, + T__11 = 12, + T__12 = 13, + T__13 = 14, + T__14 = 15, + Comment = 16, + Space = 17, + Cdo = 18, + Cdc = 19, + Includes = 20, + DashMatch = 21, + Hash = 22, + Import = 23, + Page = 24, + Media = 25, + Namespace = 26, + Charset = 27, + Important = 28, + Percentage = 29, + Uri = 30, + UnicodeRange = 31, + MediaOnly = 32, + Not = 33, + And = 34, + Dimension = 35, + UnknownDimension = 36, + Plus = 37, + Minus = 38, + Greater = 39, + Comma = 40, + Tilde = 41, + PseudoNot = 42, + Number = 43, + String_ = 44, + PrefixMatch = 45, + SuffixMatch = 46, + SubstringMatch = 47, + FontFace = 48, + Supports = 49, + Or = 50, + Keyframes = 51, + From = 52, + To = 53, + Calc = 54, + Viewport = 55, + CounterStyle = 56, + FontFeatureValues = 57, + DxImageTransform = 58, + Variable = 59, + Var = 60, + Ident = 61, + Function_ = 62; + public static final int RULE_stylesheet = 0, + RULE_charset = 1, + RULE_imports = 2, + RULE_namespace_ = 3, + RULE_namespacePrefix = 4, + RULE_media = 5, + RULE_mediaQueryList = 6, + RULE_mediaQuery = 7, + RULE_mediaType = 8, + RULE_mediaExpression = 9, + RULE_mediaFeature = 10, + RULE_page = 11, + RULE_pseudoPage = 12, + RULE_selectorGroup = 13, + RULE_selector = 14, + RULE_combinator = 15, + RULE_simpleSelectorSequence = 16, + RULE_typeSelector = 17, + RULE_typeNamespacePrefix = 18, + RULE_elementName = 19, + RULE_universal = 20, + RULE_className = 21, + RULE_attrib = 22, + RULE_pseudo = 23, + RULE_functionalPseudo = 24, + RULE_expression = 25, + RULE_negation = 26, + RULE_negationArg = 27, + RULE_operator_ = 28, + RULE_property_ = 29, + RULE_ruleset = 30, + RULE_declarationList = 31, + RULE_declaration = 32, + RULE_prio = 33, + RULE_value = 34, + RULE_expr = 35, + RULE_term = 36, + RULE_function_ = 37, + RULE_dxImageTransform = 38, + RULE_hexcolor = 39, + RULE_number = 40, + RULE_percentage = 41, + RULE_dimension = 42, + RULE_unknownDimension = 43, + RULE_any_ = 44, + RULE_atRule = 45, + RULE_atKeyword = 46, + RULE_unused = 47, + RULE_block = 48, + RULE_nestedStatement = 49, + RULE_groupRuleBody = 50, + RULE_supportsRule = 51, + RULE_supportsCondition = 52, + RULE_supportsConditionInParens = 53, + RULE_supportsNegation = 54, + RULE_supportsConjunction = 55, + RULE_supportsDisjunction = 56, + RULE_supportsDeclarationCondition = 57, + RULE_generalEnclosed = 58, + RULE_var_ = 59, + RULE_calc = 60, + RULE_calcSum = 61, + RULE_calcProduct = 62, + RULE_calcValue = 63, + RULE_fontFaceRule = 64, + RULE_fontFaceDeclaration = 65, + RULE_keyframesRule = 66, + RULE_keyframesBlocks = 67, + RULE_keyframeSelector = 68, + RULE_viewport = 69, + RULE_counterStyle = 70, + RULE_fontFeatureValuesRule = 71, + RULE_fontFamilyNameList = 72, + RULE_fontFamilyName = 73, + RULE_featureValueBlock = 74, + RULE_featureType = 75, + RULE_featureValueDefinition = 76, + RULE_ident = 77, + RULE_ws = 78; + private static String[] makeRuleNames() { - return new String[] { - "stylesheet", "charset", "imports", "namespace_", "namespacePrefix", - "media", "mediaQueryList", "mediaQuery", "mediaType", "mediaExpression", - "mediaFeature", "page", "pseudoPage", "selectorGroup", "selector", "combinator", - "simpleSelectorSequence", "typeSelector", "typeNamespacePrefix", "elementName", - "universal", "className", "attrib", "pseudo", "functionalPseudo", "expression", - "negation", "negationArg", "operator_", "property_", "ruleset", "declarationList", - "declaration", "prio", "value", "expr", "term", "function_", "dxImageTransform", - "hexcolor", "number", "percentage", "dimension", "unknownDimension", - "any_", "atRule", "atKeyword", "unused", "block", "nestedStatement", - "groupRuleBody", "supportsRule", "supportsCondition", "supportsConditionInParens", - "supportsNegation", "supportsConjunction", "supportsDisjunction", "supportsDeclarationCondition", - "generalEnclosed", "var_", "calc", "calcSum", "calcProduct", "calcValue", - "fontFaceRule", "fontFaceDeclaration", "keyframesRule", "keyframesBlocks", - "keyframeSelector", "viewport", "counterStyle", "fontFeatureValuesRule", - "fontFamilyNameList", "fontFamilyName", "featureValueBlock", "featureType", - "featureValueDefinition", "ident", "ws" - }; + return new String[] { + "stylesheet", + "charset", + "imports", + "namespace_", + "namespacePrefix", + "media", + "mediaQueryList", + "mediaQuery", + "mediaType", + "mediaExpression", + "mediaFeature", + "page", + "pseudoPage", + "selectorGroup", + "selector", + "combinator", + "simpleSelectorSequence", + "typeSelector", + "typeNamespacePrefix", + "elementName", + "universal", + "className", + "attrib", + "pseudo", + "functionalPseudo", + "expression", + "negation", + "negationArg", + "operator_", + "property_", + "ruleset", + "declarationList", + "declaration", + "prio", + "value", + "expr", + "term", + "function_", + "dxImageTransform", + "hexcolor", + "number", + "percentage", + "dimension", + "unknownDimension", + "any_", + "atRule", + "atKeyword", + "unused", + "block", + "nestedStatement", + "groupRuleBody", + "supportsRule", + "supportsCondition", + "supportsConditionInParens", + "supportsNegation", + "supportsConjunction", + "supportsDisjunction", + "supportsDeclarationCondition", + "generalEnclosed", + "var_", + "calc", + "calcSum", + "calcProduct", + "calcValue", + "fontFaceRule", + "fontFaceDeclaration", + "keyframesRule", + "keyframesBlocks", + "keyframeSelector", + "viewport", + "counterStyle", + "fontFeatureValuesRule", + "fontFamilyNameList", + "fontFamilyName", + "featureValueBlock", + "featureType", + "featureValueDefinition", + "ident", + "ws" + }; } public static final String[] ruleNames = makeRuleNames(); private static String[] makeLiteralNames() { - return new String[] { - null, "';'", "'('", "':'", "')'", "'{'", "'}'", "'*'", "'|'", "'.'", - "'['", "'='", "']'", "'/'", "'_'", "'@'", null, null, "''", - "'~='", "'|='", null, null, null, null, null, "'@charset '", null, null, - null, null, null, null, null, null, null, "'+'", "'-'", "'>'", "','", - "'~'", null, null, null, "'^='", "'$='", "'*='", null, null, null, null, - null, null, "'calc('", null, null, null, null, null, "'var('" - }; + return new String[] { + null, + "';'", + "'('", + "':'", + "')'", + "'{'", + "'}'", + "'*'", + "'|'", + "'.'", + "'['", + "'='", + "']'", + "'/'", + "'_'", + "'@'", + null, + null, + "''", + "'~='", + "'|='", + null, + null, + null, + null, + null, + "'@charset '", + null, + null, + null, + null, + null, + null, + null, + null, + null, + "'+'", + "'-'", + "'>'", + "','", + "'~'", + null, + null, + null, + "'^='", + "'$='", + "'*='", + null, + null, + null, + null, + null, + null, + "'calc('", + null, + null, + null, + null, + null, + "'var('" + }; } private static final String[] _LITERAL_NAMES = makeLiteralNames(); private static String[] makeSymbolicNames() { - return new String[] { - null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, "Comment", "Space", "Cdo", "Cdc", "Includes", - "DashMatch", "Hash", "Import", "Page", "Media", "Namespace", "Charset", - "Important", "Percentage", "Uri", "UnicodeRange", "MediaOnly", "Not", - "And", "Dimension", "UnknownDimension", "Plus", "Minus", "Greater", "Comma", - "Tilde", "PseudoNot", "Number", "String_", "PrefixMatch", "SuffixMatch", - "SubstringMatch", "FontFace", "Supports", "Or", "Keyframes", "From", - "To", "Calc", "Viewport", "CounterStyle", "FontFeatureValues", "DxImageTransform", - "Variable", "Var", "Ident", "Function_" - }; + return new String[] { + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + "Comment", + "Space", + "Cdo", + "Cdc", + "Includes", + "DashMatch", + "Hash", + "Import", + "Page", + "Media", + "Namespace", + "Charset", + "Important", + "Percentage", + "Uri", + "UnicodeRange", + "MediaOnly", + "Not", + "And", + "Dimension", + "UnknownDimension", + "Plus", + "Minus", + "Greater", + "Comma", + "Tilde", + "PseudoNot", + "Number", + "String_", + "PrefixMatch", + "SuffixMatch", + "SubstringMatch", + "FontFace", + "Supports", + "Or", + "Keyframes", + "From", + "To", + "Calc", + "Viewport", + "CounterStyle", + "FontFeatureValues", + "DxImageTransform", + "Variable", + "Var", + "Ident", + "Function_" + }; } private static final String[] _SYMBOLIC_NAMES = makeSymbolicNames(); public static final Vocabulary VOCABULARY = new VocabularyImpl(_LITERAL_NAMES, _SYMBOLIC_NAMES); @@ -401,7 +679,7 @@ public CharsetContext(ParserRuleContext parent, int invokingState) { super(parent, invokingState); } @Override public int getRuleIndex() { return RULE_charset; } - + public CharsetContext() { } public void copyFrom(CharsetContext ctx) { super.copyFrom(ctx); @@ -516,7 +794,7 @@ public ImportsContext(ParserRuleContext parent, int invokingState) { super(parent, invokingState); } @Override public int getRuleIndex() { return RULE_imports; } - + public ImportsContext() { } public void copyFrom(ImportsContext ctx) { super.copyFrom(ctx); @@ -708,7 +986,7 @@ public Namespace_Context(ParserRuleContext parent, int invokingState) { super(parent, invokingState); } @Override public int getRuleIndex() { return RULE_namespace_; } - + public Namespace_Context() { } public void copyFrom(Namespace_Context ctx) { super.copyFrom(ctx); @@ -1161,7 +1439,7 @@ public final MediaQueryContext mediaQuery() throws RecognitionException { setState(305); mediaExpression(); } - } + } } setState(311); _errHandler.sync(this); @@ -1188,7 +1466,7 @@ public final MediaQueryContext mediaQuery() throws RecognitionException { setState(315); mediaExpression(); } - } + } } setState(321); _errHandler.sync(this); @@ -1939,7 +2217,7 @@ public final SimpleSelectorSequenceContext simpleSelectorSequence() throws Recog case PseudoNot: enterOuterAlt(_localctx, 2); { - setState(418); + setState(418); _errHandler.sync(this); _la = _input.LA(1); do { @@ -1981,7 +2259,7 @@ public final SimpleSelectorSequenceContext simpleSelectorSequence() throws Recog throw new NoViableAltException(this); } } - setState(420); + setState(420); _errHandler.sync(this); _la = _input.LA(1); } while ( (((_la) & ~0x3f) == 0 && ((1L << _la) & 4398050706952L) != 0) ); @@ -2608,7 +2886,7 @@ public final ExpressionContext expression() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(487); + setState(487); _errHandler.sync(this); _la = _input.LA(1); do { @@ -2672,7 +2950,7 @@ public final ExpressionContext expression() throws RecognitionException { ws(); } } - setState(489); + setState(489); _errHandler.sync(this); _la = _input.LA(1); } while ( (((_la) & ~0x3f) == 0 && ((1L << _la) & 2320506641742561280L) != 0) ); @@ -2854,7 +3132,7 @@ public Operator_Context(ParserRuleContext parent, int invokingState) { super(parent, invokingState); } @Override public int getRuleIndex() { return RULE_operator_; } - + public Operator_Context() { } public void copyFrom(Operator_Context ctx) { super.copyFrom(ctx); @@ -2971,7 +3249,7 @@ public Property_Context(ParserRuleContext parent, int invokingState) { super(parent, invokingState); } @Override public int getRuleIndex() { return RULE_property_; } - + public Property_Context() { } public void copyFrom(Property_Context ctx) { super.copyFrom(ctx); @@ -3096,7 +3374,7 @@ public RulesetContext(ParserRuleContext parent, int invokingState) { super(parent, invokingState); } @Override public int getRuleIndex() { return RULE_ruleset; } - + public RulesetContext() { } public void copyFrom(RulesetContext ctx) { super.copyFrom(ctx); @@ -3332,7 +3610,7 @@ public final DeclarationListContext declarationList() throws RecognitionExceptio break; } } - } + } } setState(569); _errHandler.sync(this); @@ -3357,7 +3635,7 @@ public DeclarationContext(ParserRuleContext parent, int invokingState) { super(parent, invokingState); } @Override public int getRuleIndex() { return RULE_declaration; } - + public DeclarationContext() { } public void copyFrom(DeclarationContext ctx) { super.copyFrom(ctx); @@ -3578,7 +3856,7 @@ public final ValueContext value() throws RecognitionException { int _alt; enterOuterAlt(_localctx, 1); { - setState(592); + setState(592); _errHandler.sync(this); _alt = 1; do { @@ -3638,7 +3916,7 @@ public final ValueContext value() throws RecognitionException { default: throw new NoViableAltException(this); } - setState(594); + setState(594); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,56,_ctx); } while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ); @@ -3718,7 +3996,7 @@ public final ExprContext expr() throws RecognitionException { setState(600); term(); } - } + } } setState(605); _errHandler.sync(this); @@ -3743,7 +4021,7 @@ public TermContext(ParserRuleContext parent, int invokingState) { super(parent, invokingState); } @Override public int getRuleIndex() { return RULE_term; } - + public TermContext() { } public void copyFrom(TermContext ctx) { super.copyFrom(ctx); @@ -4801,7 +5079,7 @@ public AtRuleContext(ParserRuleContext parent, int invokingState) { super(parent, invokingState); } @Override public int getRuleIndex() { return RULE_atRule; } - + public AtRuleContext() { } public void copyFrom(AtRuleContext ctx) { super.copyFrom(ctx); @@ -5770,7 +6048,7 @@ public final SupportsConjunctionContext supportsConjunction() throws Recognition { setState(834); supportsConditionInParens(); - setState(844); + setState(844); _errHandler.sync(this); _alt = 1; do { @@ -5800,7 +6078,7 @@ public final SupportsConjunctionContext supportsConjunction() throws Recognition default: throw new NoViableAltException(this); } - setState(846); + setState(846); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,80,_ctx); } while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ); @@ -5867,7 +6145,7 @@ public final SupportsDisjunctionContext supportsDisjunction() throws Recognition { setState(848); supportsConditionInParens(); - setState(858); + setState(858); _errHandler.sync(this); _alt = 1; do { @@ -5897,7 +6175,7 @@ public final SupportsDisjunctionContext supportsDisjunction() throws Recognition default: throw new NoViableAltException(this); } - setState(860); + setState(860); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,81,_ctx); } while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ); @@ -6629,7 +6907,7 @@ public FontFaceDeclarationContext(ParserRuleContext parent, int invokingState) { super(parent, invokingState); } @Override public int getRuleIndex() { return RULE_fontFaceDeclaration; } - + public FontFaceDeclarationContext() { } public void copyFrom(FontFaceDeclarationContext ctx) { super.copyFrom(ctx); @@ -7308,7 +7586,7 @@ public final FontFamilyNameListContext fontFamilyNameList() throws RecognitionEx setState(1050); fontFamilyName(); } - } + } } setState(1056); _errHandler.sync(this); @@ -7399,7 +7677,7 @@ public final FontFamilyNameContext fontFamilyName() throws RecognitionException setState(1060); ident(); } - } + } } setState(1066); _errHandler.sync(this); @@ -7638,7 +7916,7 @@ public final FeatureValueDefinitionContext featureValueDefinition() throws Recog setState(1098); number(); } - } + } } setState(1104); _errHandler.sync(this); @@ -7770,7 +8048,7 @@ public final WsContext ws() throws RecognitionException { consume(); } } - } + } } setState(1112); _errHandler.sync(this); diff --git a/core/src/test/java/com/spinyowl/spinygui/core/layout/impl/BlockLayoutTest.java b/core/src/test/java/com/spinyowl/spinygui/core/layout/impl/BlockLayoutTest.java index bc16ed22..ecfbd1e7 100644 --- a/core/src/test/java/com/spinyowl/spinygui/core/layout/impl/BlockLayoutTest.java +++ b/core/src/test/java/com/spinyowl/spinygui/core/layout/impl/BlockLayoutTest.java @@ -61,6 +61,89 @@ void layout_whenNestedBlockHasInlineTextBeforeBlockChild_doesNotDoubleCountParen assertEquals(18, blockChild.box().borderBox().y()); } + @Test + void layout_whenInlineRunPrecedesBlockChild_includesInlineHeightInBlockFlow() { + Frame frame = NodeBuilder.frame(); + frame.frameSize(200, 200); + style(frame, 0); + Element wrapper = NodeBuilder.div(); + style(wrapper, 0); + Text left = NodeBuilder.text("a"); + Element inline = new Element("span"); + style(inline, 0); + inline.resolvedStyle().display(Display.INLINE); + inline.addChild(NodeBuilder.text("b")); + Element blockChild = NodeBuilder.div(); + style(blockChild, 5); + wrapper.addChildren(left, inline, blockChild); + frame.addChild(wrapper); + + RecursiveLayoutService layoutService = new RecursiveLayoutService(); + BlockLayout blockLayout = + new BlockLayout(layoutService, new InlineFormattingContext(new FixedTextMeasurer())); + layoutService.blockLayout(blockLayout); + + blockLayout.layout(frame, new LayoutContext()); + + assertEquals(10, blockChild.box().borderBox().y()); + assertEquals(20, wrapper.box().content().height()); + } + + @Test + void layout_whenInlineContentFollowsBlock_startsAfterBlock() { + Frame frame = NodeBuilder.frame(); + frame.frameSize(200, 200); + style(frame, 0); + Element wrapper = NodeBuilder.div(); + style(wrapper, 0); + Element blockChild = NodeBuilder.div(); + style(blockChild, 5); + Text text = NodeBuilder.text("a"); + wrapper.addChildren(blockChild, text); + frame.addChild(wrapper); + + RecursiveLayoutService layoutService = new RecursiveLayoutService(); + BlockLayout blockLayout = + new BlockLayout(layoutService, new InlineFormattingContext(new FixedTextMeasurer())); + layoutService.blockLayout(blockLayout); + + blockLayout.layout(frame, new LayoutContext()); + + assertEquals(10, text.inlineFragments().get(0).y()); + } + + @Test + void layout_whenDisplayNoneDescendantInsideInline_ignoresDescendant() { + Frame frame = NodeBuilder.frame(); + frame.frameSize(200, 200); + style(frame, 0); + Element wrapper = NodeBuilder.div(); + style(wrapper, 0); + Element inline = new Element("span"); + style(inline, 0); + inline.resolvedStyle().display(Display.INLINE); + Text left = NodeBuilder.text("a"); + Element hidden = new Element("span"); + style(hidden, 0); + hidden.resolvedStyle().display(Display.NONE); + Text hiddenText = NodeBuilder.text("hidden"); + hidden.addChild(hiddenText); + Text right = NodeBuilder.text("b"); + inline.addChildren(left, hidden, right); + wrapper.addChild(inline); + frame.addChild(wrapper); + + RecursiveLayoutService layoutService = new RecursiveLayoutService(); + BlockLayout blockLayout = + new BlockLayout(layoutService, new InlineFormattingContext(new FixedTextMeasurer())); + layoutService.blockLayout(blockLayout); + + blockLayout.layout(frame, new LayoutContext()); + + assertEquals(0, hiddenText.inlineFragments().size()); + assertEquals(20, inline.box().content().width()); + } + private void style(Element element, float borderWidth) { ResolvedStyle style = element.resolvedStyle(); style.display(Display.BLOCK); diff --git a/core/src/test/java/com/spinyowl/spinygui/core/layout/impl/InlineFormattingContextTest.java b/core/src/test/java/com/spinyowl/spinygui/core/layout/impl/InlineFormattingContextTest.java index 9017f8cf..fe033107 100644 --- a/core/src/test/java/com/spinyowl/spinygui/core/layout/impl/InlineFormattingContextTest.java +++ b/core/src/test/java/com/spinyowl/spinygui/core/layout/impl/InlineFormattingContextTest.java @@ -1,6 +1,7 @@ package com.spinyowl.spinygui.core.layout.impl; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import com.spinyowl.spinygui.core.font.Font; import com.spinyowl.spinygui.core.font.FontStyle; @@ -16,6 +17,7 @@ import com.spinyowl.spinygui.core.style.types.Position; import com.spinyowl.spinygui.core.style.types.TextAlign; import com.spinyowl.spinygui.core.style.types.WhiteSpace; +import com.spinyowl.spinygui.core.style.types.border.BorderStyle; import com.spinyowl.spinygui.core.style.types.length.Length; import com.spinyowl.spinygui.core.system.font.FontMetrics; import com.spinyowl.spinygui.core.system.font.TextLineMetrics; @@ -93,7 +95,10 @@ void layout_whenConsecutiveTextNodes_shareLineBox() { assertEquals(0, first.inlineFragments().get(0).y()); assertEquals(0, second.inlineFragments().get(0).y()); - assertEquals(60, second.inlineFragments().get(0).x()); + assertEquals(" ", second.inlineFragments().get(0).text()); + assertEquals(50, second.inlineFragments().get(0).x()); + assertEquals("world", second.inlineFragments().get(1).text()); + assertEquals(60, second.inlineFragments().get(1).x()); } @Test @@ -112,6 +117,69 @@ void layout_whenInlineElementBetweenText_participatesInLine() { assertEquals(10, middle.inlineFragments().get(0).x()); assertEquals(20, right.inlineFragments().get(0).x()); assertEquals(10, inline.box().content().width()); + assertEquals(1, inline.inlineFragments().size()); + assertEquals(10, inline.inlineFragments().get(0).x()); + } + + @Test + void layout_whenInlineElementWraps_createsElementFragmentForEachLine() { + parent.box().content().width(25); + Element inline = new Element("span"); + style(inline, Display.INLINE); + inline.resolvedStyle().overflowWrap(OverflowWrap.BREAK_WORD); + Text text = NodeBuilder.text("abcd"); + inline.addChild(text); + parent.addChild(inline); + + formattingContext.layout(parent, List.of(inline), 0); + + assertTrue(inline.inlineFragments().size() > 1); + assertEquals(0, inline.inlineFragments().get(0).y()); + assertEquals(10, inline.inlineFragments().get(1).y()); + } + + @Test + void layout_whenNestedInlineHasOwnColor_usesNestedStyleForTextFragment() { + parent.box().content().width(200); + Element inline = new Element("span"); + style(inline, Display.INLINE); + inline.resolvedStyle().color(Color.RED); + Text text = NodeBuilder.text("red"); + inline.addChild(text); + parent.addChild(inline); + + formattingContext.layout(parent, List.of(inline), 0); + + assertEquals(Color.RED, text.inlineFragments().get(0).color()); + } + + @Test + void layout_whenInlineElementHasMarginPaddingAndBorder_advancesCursorButExcludesMarginFragment() { + parent.box().content().width(200); + Element inline = new Element("span"); + style(inline, Display.INLINE); + inline.resolvedStyle().marginLeft(Length.pixel(5)); + inline.resolvedStyle().marginRight(Length.pixel(7)); + inline.resolvedStyle().paddingLeft(Length.pixel(3)); + inline.resolvedStyle().paddingRight(Length.pixel(4)); + inline.resolvedStyle().borderLeftWidth(Length.pixel(2)); + inline.resolvedStyle().borderRightWidth(Length.pixel(2)); + inline.resolvedStyle().borderTopStyle(BorderStyle.SOLID); + inline.resolvedStyle().borderRightStyle(BorderStyle.SOLID); + inline.resolvedStyle().borderBottomStyle(BorderStyle.SOLID); + inline.resolvedStyle().borderLeftStyle(BorderStyle.SOLID); + Text text = NodeBuilder.text("a"); + Text right = NodeBuilder.text("b"); + inline.addChild(text); + parent.addChildren(inline, right); + + formattingContext.layout(parent, List.of(inline, right), 0); + + assertEquals(10, text.inlineFragments().get(0).x()); + assertEquals(33, right.inlineFragments().get(0).x()); + assertEquals(5, inline.inlineFragments().get(0).x()); + assertEquals(21, inline.inlineFragments().get(0).width()); + assertEquals(21, inline.box().content().width()); } @Test @@ -184,6 +252,7 @@ void layout_whenInlineElementHasPercentPadding_usesParentContentAsBase() { assertEquals(10, text.inlineFragments().get(0).x()); assertEquals(30, inline.box().content().width()); + assertEquals(30, inline.inlineFragments().get(0).width()); } @Test From e8c9ac33776f20828d74a48c47d3f625ba890cc7 Mon Sep 17 00:00:00 2001 From: SpinyOwl Date: Tue, 19 May 2026 20:22:08 +0200 Subject: [PATCH 012/121] Regenerate CSS parser sources --- .../core/parser/impl/css/antlr/CSS3Lexer.java | 19 +- .../parser/impl/css/antlr/CSS3Parser.java | 508 ++++-------------- 2 files changed, 115 insertions(+), 412 deletions(-) diff --git a/core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3Lexer.java b/core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3Lexer.java index def67903..4bc76942 100644 --- a/core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3Lexer.java +++ b/core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3Lexer.java @@ -1,22 +1,17 @@ // Generated from com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3.g4 by ANTLR 4.13.2 package com.spinyowl.spinygui.core.parser.impl.css.antlr; - -import org.antlr.v4.runtime.CharStream; import org.antlr.v4.runtime.Lexer; -import org.antlr.v4.runtime.RuntimeMetaData; -import org.antlr.v4.runtime.Vocabulary; -import org.antlr.v4.runtime.VocabularyImpl; -import org.antlr.v4.runtime.atn.ATN; -import org.antlr.v4.runtime.atn.ATNDeserializer; -import org.antlr.v4.runtime.atn.LexerATNSimulator; -import org.antlr.v4.runtime.atn.PredictionContextCache; +import org.antlr.v4.runtime.CharStream; +import org.antlr.v4.runtime.Token; +import org.antlr.v4.runtime.TokenStream; +import org.antlr.v4.runtime.*; +import org.antlr.v4.runtime.atn.*; import org.antlr.v4.runtime.dfa.DFA; +import org.antlr.v4.runtime.misc.*; @SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast", "CheckReturnValue", "this-escape"}) public class CSS3Lexer extends Lexer { - static { - RuntimeMetaData.checkVersion("4.13.2", RuntimeMetaData.VERSION); - } + static { RuntimeMetaData.checkVersion("4.13.2", RuntimeMetaData.VERSION); } protected static final DFA[] _decisionToDFA; protected static final PredictionContextCache _sharedContextCache = diff --git a/core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3Parser.java b/core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3Parser.java index 264d79d4..2014e236 100644 --- a/core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3Parser.java +++ b/core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3Parser.java @@ -1,393 +1,101 @@ // Generated from com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3.g4 by ANTLR 4.13.2 package com.spinyowl.spinygui.core.parser.impl.css.antlr; - -import java.util.List; -import org.antlr.v4.runtime.NoViableAltException; -import org.antlr.v4.runtime.Parser; -import org.antlr.v4.runtime.ParserRuleContext; -import org.antlr.v4.runtime.RecognitionException; -import org.antlr.v4.runtime.RuntimeMetaData; -import org.antlr.v4.runtime.Token; -import org.antlr.v4.runtime.TokenStream; -import org.antlr.v4.runtime.Vocabulary; -import org.antlr.v4.runtime.VocabularyImpl; -import org.antlr.v4.runtime.atn.ATN; -import org.antlr.v4.runtime.atn.ATNDeserializer; -import org.antlr.v4.runtime.atn.ParserATNSimulator; -import org.antlr.v4.runtime.atn.PredictionContextCache; +import org.antlr.v4.runtime.atn.*; import org.antlr.v4.runtime.dfa.DFA; -import org.antlr.v4.runtime.tree.ParseTreeListener; -import org.antlr.v4.runtime.tree.ParseTreeVisitor; -import org.antlr.v4.runtime.tree.TerminalNode; +import org.antlr.v4.runtime.*; +import org.antlr.v4.runtime.misc.*; +import org.antlr.v4.runtime.tree.*; +import java.util.List; +import java.util.Iterator; +import java.util.ArrayList; @SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast", "CheckReturnValue", "this-escape"}) public class CSS3Parser extends Parser { - static { - RuntimeMetaData.checkVersion("4.13.2", RuntimeMetaData.VERSION); - } + static { RuntimeMetaData.checkVersion("4.13.2", RuntimeMetaData.VERSION); } protected static final DFA[] _decisionToDFA; protected static final PredictionContextCache _sharedContextCache = new PredictionContextCache(); - public static final int T__0 = 1, - T__1 = 2, - T__2 = 3, - T__3 = 4, - T__4 = 5, - T__5 = 6, - T__6 = 7, - T__7 = 8, - T__8 = 9, - T__9 = 10, - T__10 = 11, - T__11 = 12, - T__12 = 13, - T__13 = 14, - T__14 = 15, - Comment = 16, - Space = 17, - Cdo = 18, - Cdc = 19, - Includes = 20, - DashMatch = 21, - Hash = 22, - Import = 23, - Page = 24, - Media = 25, - Namespace = 26, - Charset = 27, - Important = 28, - Percentage = 29, - Uri = 30, - UnicodeRange = 31, - MediaOnly = 32, - Not = 33, - And = 34, - Dimension = 35, - UnknownDimension = 36, - Plus = 37, - Minus = 38, - Greater = 39, - Comma = 40, - Tilde = 41, - PseudoNot = 42, - Number = 43, - String_ = 44, - PrefixMatch = 45, - SuffixMatch = 46, - SubstringMatch = 47, - FontFace = 48, - Supports = 49, - Or = 50, - Keyframes = 51, - From = 52, - To = 53, - Calc = 54, - Viewport = 55, - CounterStyle = 56, - FontFeatureValues = 57, - DxImageTransform = 58, - Variable = 59, - Var = 60, - Ident = 61, - Function_ = 62; - public static final int RULE_stylesheet = 0, - RULE_charset = 1, - RULE_imports = 2, - RULE_namespace_ = 3, - RULE_namespacePrefix = 4, - RULE_media = 5, - RULE_mediaQueryList = 6, - RULE_mediaQuery = 7, - RULE_mediaType = 8, - RULE_mediaExpression = 9, - RULE_mediaFeature = 10, - RULE_page = 11, - RULE_pseudoPage = 12, - RULE_selectorGroup = 13, - RULE_selector = 14, - RULE_combinator = 15, - RULE_simpleSelectorSequence = 16, - RULE_typeSelector = 17, - RULE_typeNamespacePrefix = 18, - RULE_elementName = 19, - RULE_universal = 20, - RULE_className = 21, - RULE_attrib = 22, - RULE_pseudo = 23, - RULE_functionalPseudo = 24, - RULE_expression = 25, - RULE_negation = 26, - RULE_negationArg = 27, - RULE_operator_ = 28, - RULE_property_ = 29, - RULE_ruleset = 30, - RULE_declarationList = 31, - RULE_declaration = 32, - RULE_prio = 33, - RULE_value = 34, - RULE_expr = 35, - RULE_term = 36, - RULE_function_ = 37, - RULE_dxImageTransform = 38, - RULE_hexcolor = 39, - RULE_number = 40, - RULE_percentage = 41, - RULE_dimension = 42, - RULE_unknownDimension = 43, - RULE_any_ = 44, - RULE_atRule = 45, - RULE_atKeyword = 46, - RULE_unused = 47, - RULE_block = 48, - RULE_nestedStatement = 49, - RULE_groupRuleBody = 50, - RULE_supportsRule = 51, - RULE_supportsCondition = 52, - RULE_supportsConditionInParens = 53, - RULE_supportsNegation = 54, - RULE_supportsConjunction = 55, - RULE_supportsDisjunction = 56, - RULE_supportsDeclarationCondition = 57, - RULE_generalEnclosed = 58, - RULE_var_ = 59, - RULE_calc = 60, - RULE_calcSum = 61, - RULE_calcProduct = 62, - RULE_calcValue = 63, - RULE_fontFaceRule = 64, - RULE_fontFaceDeclaration = 65, - RULE_keyframesRule = 66, - RULE_keyframesBlocks = 67, - RULE_keyframeSelector = 68, - RULE_viewport = 69, - RULE_counterStyle = 70, - RULE_fontFeatureValuesRule = 71, - RULE_fontFamilyNameList = 72, - RULE_fontFamilyName = 73, - RULE_featureValueBlock = 74, - RULE_featureType = 75, - RULE_featureValueDefinition = 76, - RULE_ident = 77, - RULE_ws = 78; - + public static final int + T__0=1, T__1=2, T__2=3, T__3=4, T__4=5, T__5=6, T__6=7, T__7=8, T__8=9, + T__9=10, T__10=11, T__11=12, T__12=13, T__13=14, T__14=15, Comment=16, + Space=17, Cdo=18, Cdc=19, Includes=20, DashMatch=21, Hash=22, Import=23, + Page=24, Media=25, Namespace=26, Charset=27, Important=28, Percentage=29, + Uri=30, UnicodeRange=31, MediaOnly=32, Not=33, And=34, Dimension=35, UnknownDimension=36, + Plus=37, Minus=38, Greater=39, Comma=40, Tilde=41, PseudoNot=42, Number=43, + String_=44, PrefixMatch=45, SuffixMatch=46, SubstringMatch=47, FontFace=48, + Supports=49, Or=50, Keyframes=51, From=52, To=53, Calc=54, Viewport=55, + CounterStyle=56, FontFeatureValues=57, DxImageTransform=58, Variable=59, + Var=60, Ident=61, Function_=62; + public static final int + RULE_stylesheet = 0, RULE_charset = 1, RULE_imports = 2, RULE_namespace_ = 3, + RULE_namespacePrefix = 4, RULE_media = 5, RULE_mediaQueryList = 6, RULE_mediaQuery = 7, + RULE_mediaType = 8, RULE_mediaExpression = 9, RULE_mediaFeature = 10, + RULE_page = 11, RULE_pseudoPage = 12, RULE_selectorGroup = 13, RULE_selector = 14, + RULE_combinator = 15, RULE_simpleSelectorSequence = 16, RULE_typeSelector = 17, + RULE_typeNamespacePrefix = 18, RULE_elementName = 19, RULE_universal = 20, + RULE_className = 21, RULE_attrib = 22, RULE_pseudo = 23, RULE_functionalPseudo = 24, + RULE_expression = 25, RULE_negation = 26, RULE_negationArg = 27, RULE_operator_ = 28, + RULE_property_ = 29, RULE_ruleset = 30, RULE_declarationList = 31, RULE_declaration = 32, + RULE_prio = 33, RULE_value = 34, RULE_expr = 35, RULE_term = 36, RULE_function_ = 37, + RULE_dxImageTransform = 38, RULE_hexcolor = 39, RULE_number = 40, RULE_percentage = 41, + RULE_dimension = 42, RULE_unknownDimension = 43, RULE_any_ = 44, RULE_atRule = 45, + RULE_atKeyword = 46, RULE_unused = 47, RULE_block = 48, RULE_nestedStatement = 49, + RULE_groupRuleBody = 50, RULE_supportsRule = 51, RULE_supportsCondition = 52, + RULE_supportsConditionInParens = 53, RULE_supportsNegation = 54, RULE_supportsConjunction = 55, + RULE_supportsDisjunction = 56, RULE_supportsDeclarationCondition = 57, + RULE_generalEnclosed = 58, RULE_var_ = 59, RULE_calc = 60, RULE_calcSum = 61, + RULE_calcProduct = 62, RULE_calcValue = 63, RULE_fontFaceRule = 64, RULE_fontFaceDeclaration = 65, + RULE_keyframesRule = 66, RULE_keyframesBlocks = 67, RULE_keyframeSelector = 68, + RULE_viewport = 69, RULE_counterStyle = 70, RULE_fontFeatureValuesRule = 71, + RULE_fontFamilyNameList = 72, RULE_fontFamilyName = 73, RULE_featureValueBlock = 74, + RULE_featureType = 75, RULE_featureValueDefinition = 76, RULE_ident = 77, + RULE_ws = 78; private static String[] makeRuleNames() { - return new String[] { - "stylesheet", - "charset", - "imports", - "namespace_", - "namespacePrefix", - "media", - "mediaQueryList", - "mediaQuery", - "mediaType", - "mediaExpression", - "mediaFeature", - "page", - "pseudoPage", - "selectorGroup", - "selector", - "combinator", - "simpleSelectorSequence", - "typeSelector", - "typeNamespacePrefix", - "elementName", - "universal", - "className", - "attrib", - "pseudo", - "functionalPseudo", - "expression", - "negation", - "negationArg", - "operator_", - "property_", - "ruleset", - "declarationList", - "declaration", - "prio", - "value", - "expr", - "term", - "function_", - "dxImageTransform", - "hexcolor", - "number", - "percentage", - "dimension", - "unknownDimension", - "any_", - "atRule", - "atKeyword", - "unused", - "block", - "nestedStatement", - "groupRuleBody", - "supportsRule", - "supportsCondition", - "supportsConditionInParens", - "supportsNegation", - "supportsConjunction", - "supportsDisjunction", - "supportsDeclarationCondition", - "generalEnclosed", - "var_", - "calc", - "calcSum", - "calcProduct", - "calcValue", - "fontFaceRule", - "fontFaceDeclaration", - "keyframesRule", - "keyframesBlocks", - "keyframeSelector", - "viewport", - "counterStyle", - "fontFeatureValuesRule", - "fontFamilyNameList", - "fontFamilyName", - "featureValueBlock", - "featureType", - "featureValueDefinition", - "ident", - "ws" - }; + return new String[] { + "stylesheet", "charset", "imports", "namespace_", "namespacePrefix", + "media", "mediaQueryList", "mediaQuery", "mediaType", "mediaExpression", + "mediaFeature", "page", "pseudoPage", "selectorGroup", "selector", "combinator", + "simpleSelectorSequence", "typeSelector", "typeNamespacePrefix", "elementName", + "universal", "className", "attrib", "pseudo", "functionalPseudo", "expression", + "negation", "negationArg", "operator_", "property_", "ruleset", "declarationList", + "declaration", "prio", "value", "expr", "term", "function_", "dxImageTransform", + "hexcolor", "number", "percentage", "dimension", "unknownDimension", + "any_", "atRule", "atKeyword", "unused", "block", "nestedStatement", + "groupRuleBody", "supportsRule", "supportsCondition", "supportsConditionInParens", + "supportsNegation", "supportsConjunction", "supportsDisjunction", "supportsDeclarationCondition", + "generalEnclosed", "var_", "calc", "calcSum", "calcProduct", "calcValue", + "fontFaceRule", "fontFaceDeclaration", "keyframesRule", "keyframesBlocks", + "keyframeSelector", "viewport", "counterStyle", "fontFeatureValuesRule", + "fontFamilyNameList", "fontFamilyName", "featureValueBlock", "featureType", + "featureValueDefinition", "ident", "ws" + }; } public static final String[] ruleNames = makeRuleNames(); private static String[] makeLiteralNames() { - return new String[] { - null, - "';'", - "'('", - "':'", - "')'", - "'{'", - "'}'", - "'*'", - "'|'", - "'.'", - "'['", - "'='", - "']'", - "'/'", - "'_'", - "'@'", - null, - null, - "''", - "'~='", - "'|='", - null, - null, - null, - null, - null, - "'@charset '", - null, - null, - null, - null, - null, - null, - null, - null, - null, - "'+'", - "'-'", - "'>'", - "','", - "'~'", - null, - null, - null, - "'^='", - "'$='", - "'*='", - null, - null, - null, - null, - null, - null, - "'calc('", - null, - null, - null, - null, - null, - "'var('" - }; + return new String[] { + null, "';'", "'('", "':'", "')'", "'{'", "'}'", "'*'", "'|'", "'.'", + "'['", "'='", "']'", "'/'", "'_'", "'@'", null, null, "''", + "'~='", "'|='", null, null, null, null, null, "'@charset '", null, null, + null, null, null, null, null, null, null, "'+'", "'-'", "'>'", "','", + "'~'", null, null, null, "'^='", "'$='", "'*='", null, null, null, null, + null, null, "'calc('", null, null, null, null, null, "'var('" + }; } private static final String[] _LITERAL_NAMES = makeLiteralNames(); private static String[] makeSymbolicNames() { - return new String[] { - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - "Comment", - "Space", - "Cdo", - "Cdc", - "Includes", - "DashMatch", - "Hash", - "Import", - "Page", - "Media", - "Namespace", - "Charset", - "Important", - "Percentage", - "Uri", - "UnicodeRange", - "MediaOnly", - "Not", - "And", - "Dimension", - "UnknownDimension", - "Plus", - "Minus", - "Greater", - "Comma", - "Tilde", - "PseudoNot", - "Number", - "String_", - "PrefixMatch", - "SuffixMatch", - "SubstringMatch", - "FontFace", - "Supports", - "Or", - "Keyframes", - "From", - "To", - "Calc", - "Viewport", - "CounterStyle", - "FontFeatureValues", - "DxImageTransform", - "Variable", - "Var", - "Ident", - "Function_" - }; + return new String[] { + null, null, null, null, null, null, null, null, null, null, null, null, + null, null, null, null, "Comment", "Space", "Cdo", "Cdc", "Includes", + "DashMatch", "Hash", "Import", "Page", "Media", "Namespace", "Charset", + "Important", "Percentage", "Uri", "UnicodeRange", "MediaOnly", "Not", + "And", "Dimension", "UnknownDimension", "Plus", "Minus", "Greater", "Comma", + "Tilde", "PseudoNot", "Number", "String_", "PrefixMatch", "SuffixMatch", + "SubstringMatch", "FontFace", "Supports", "Or", "Keyframes", "From", + "To", "Calc", "Viewport", "CounterStyle", "FontFeatureValues", "DxImageTransform", + "Variable", "Var", "Ident", "Function_" + }; } private static final String[] _SYMBOLIC_NAMES = makeSymbolicNames(); public static final Vocabulary VOCABULARY = new VocabularyImpl(_LITERAL_NAMES, _SYMBOLIC_NAMES); @@ -679,7 +387,7 @@ public CharsetContext(ParserRuleContext parent, int invokingState) { super(parent, invokingState); } @Override public int getRuleIndex() { return RULE_charset; } - + public CharsetContext() { } public void copyFrom(CharsetContext ctx) { super.copyFrom(ctx); @@ -794,7 +502,7 @@ public ImportsContext(ParserRuleContext parent, int invokingState) { super(parent, invokingState); } @Override public int getRuleIndex() { return RULE_imports; } - + public ImportsContext() { } public void copyFrom(ImportsContext ctx) { super.copyFrom(ctx); @@ -986,7 +694,7 @@ public Namespace_Context(ParserRuleContext parent, int invokingState) { super(parent, invokingState); } @Override public int getRuleIndex() { return RULE_namespace_; } - + public Namespace_Context() { } public void copyFrom(Namespace_Context ctx) { super.copyFrom(ctx); @@ -1439,7 +1147,7 @@ public final MediaQueryContext mediaQuery() throws RecognitionException { setState(305); mediaExpression(); } - } + } } setState(311); _errHandler.sync(this); @@ -1466,7 +1174,7 @@ public final MediaQueryContext mediaQuery() throws RecognitionException { setState(315); mediaExpression(); } - } + } } setState(321); _errHandler.sync(this); @@ -2217,7 +1925,7 @@ public final SimpleSelectorSequenceContext simpleSelectorSequence() throws Recog case PseudoNot: enterOuterAlt(_localctx, 2); { - setState(418); + setState(418); _errHandler.sync(this); _la = _input.LA(1); do { @@ -2259,7 +1967,7 @@ public final SimpleSelectorSequenceContext simpleSelectorSequence() throws Recog throw new NoViableAltException(this); } } - setState(420); + setState(420); _errHandler.sync(this); _la = _input.LA(1); } while ( (((_la) & ~0x3f) == 0 && ((1L << _la) & 4398050706952L) != 0) ); @@ -2886,7 +2594,7 @@ public final ExpressionContext expression() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(487); + setState(487); _errHandler.sync(this); _la = _input.LA(1); do { @@ -2950,7 +2658,7 @@ public final ExpressionContext expression() throws RecognitionException { ws(); } } - setState(489); + setState(489); _errHandler.sync(this); _la = _input.LA(1); } while ( (((_la) & ~0x3f) == 0 && ((1L << _la) & 2320506641742561280L) != 0) ); @@ -3132,7 +2840,7 @@ public Operator_Context(ParserRuleContext parent, int invokingState) { super(parent, invokingState); } @Override public int getRuleIndex() { return RULE_operator_; } - + public Operator_Context() { } public void copyFrom(Operator_Context ctx) { super.copyFrom(ctx); @@ -3249,7 +2957,7 @@ public Property_Context(ParserRuleContext parent, int invokingState) { super(parent, invokingState); } @Override public int getRuleIndex() { return RULE_property_; } - + public Property_Context() { } public void copyFrom(Property_Context ctx) { super.copyFrom(ctx); @@ -3374,7 +3082,7 @@ public RulesetContext(ParserRuleContext parent, int invokingState) { super(parent, invokingState); } @Override public int getRuleIndex() { return RULE_ruleset; } - + public RulesetContext() { } public void copyFrom(RulesetContext ctx) { super.copyFrom(ctx); @@ -3610,7 +3318,7 @@ public final DeclarationListContext declarationList() throws RecognitionExceptio break; } } - } + } } setState(569); _errHandler.sync(this); @@ -3635,7 +3343,7 @@ public DeclarationContext(ParserRuleContext parent, int invokingState) { super(parent, invokingState); } @Override public int getRuleIndex() { return RULE_declaration; } - + public DeclarationContext() { } public void copyFrom(DeclarationContext ctx) { super.copyFrom(ctx); @@ -3856,7 +3564,7 @@ public final ValueContext value() throws RecognitionException { int _alt; enterOuterAlt(_localctx, 1); { - setState(592); + setState(592); _errHandler.sync(this); _alt = 1; do { @@ -3916,7 +3624,7 @@ public final ValueContext value() throws RecognitionException { default: throw new NoViableAltException(this); } - setState(594); + setState(594); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,56,_ctx); } while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ); @@ -3996,7 +3704,7 @@ public final ExprContext expr() throws RecognitionException { setState(600); term(); } - } + } } setState(605); _errHandler.sync(this); @@ -4021,7 +3729,7 @@ public TermContext(ParserRuleContext parent, int invokingState) { super(parent, invokingState); } @Override public int getRuleIndex() { return RULE_term; } - + public TermContext() { } public void copyFrom(TermContext ctx) { super.copyFrom(ctx); @@ -5079,7 +4787,7 @@ public AtRuleContext(ParserRuleContext parent, int invokingState) { super(parent, invokingState); } @Override public int getRuleIndex() { return RULE_atRule; } - + public AtRuleContext() { } public void copyFrom(AtRuleContext ctx) { super.copyFrom(ctx); @@ -6048,7 +5756,7 @@ public final SupportsConjunctionContext supportsConjunction() throws Recognition { setState(834); supportsConditionInParens(); - setState(844); + setState(844); _errHandler.sync(this); _alt = 1; do { @@ -6078,7 +5786,7 @@ public final SupportsConjunctionContext supportsConjunction() throws Recognition default: throw new NoViableAltException(this); } - setState(846); + setState(846); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,80,_ctx); } while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ); @@ -6145,7 +5853,7 @@ public final SupportsDisjunctionContext supportsDisjunction() throws Recognition { setState(848); supportsConditionInParens(); - setState(858); + setState(858); _errHandler.sync(this); _alt = 1; do { @@ -6175,7 +5883,7 @@ public final SupportsDisjunctionContext supportsDisjunction() throws Recognition default: throw new NoViableAltException(this); } - setState(860); + setState(860); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,81,_ctx); } while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ); @@ -6907,7 +6615,7 @@ public FontFaceDeclarationContext(ParserRuleContext parent, int invokingState) { super(parent, invokingState); } @Override public int getRuleIndex() { return RULE_fontFaceDeclaration; } - + public FontFaceDeclarationContext() { } public void copyFrom(FontFaceDeclarationContext ctx) { super.copyFrom(ctx); @@ -7586,7 +7294,7 @@ public final FontFamilyNameListContext fontFamilyNameList() throws RecognitionEx setState(1050); fontFamilyName(); } - } + } } setState(1056); _errHandler.sync(this); @@ -7677,7 +7385,7 @@ public final FontFamilyNameContext fontFamilyName() throws RecognitionException setState(1060); ident(); } - } + } } setState(1066); _errHandler.sync(this); @@ -7916,7 +7624,7 @@ public final FeatureValueDefinitionContext featureValueDefinition() throws Recog setState(1098); number(); } - } + } } setState(1104); _errHandler.sync(this); @@ -8048,7 +7756,7 @@ public final WsContext ws() throws RecognitionException { consume(); } } - } + } } setState(1112); _errHandler.sync(this); From ff819a59471c947faad265d1e946ba85c6a2fe2f Mon Sep 17 00:00:00 2001 From: SpinyOwl Date: Tue, 19 May 2026 20:22:22 +0200 Subject: [PATCH 013/121] Document supported CSS properties --- docs/features/css-properties-support.md | 235 ++++++++++++++++++++++++ 1 file changed, 235 insertions(+) create mode 100644 docs/features/css-properties-support.md diff --git a/docs/features/css-properties-support.md b/docs/features/css-properties-support.md new file mode 100644 index 00000000..dd1c22ef --- /dev/null +++ b/docs/features/css-properties-support.md @@ -0,0 +1,235 @@ +Supported means the property is registered by a `PropertyProvider` in +`core.style.stylesheet.property` and can be parsed/applied through the stylesheet property store. + +At-rules with dedicated parser support: +- [x] `@font-face` + - [x] `font-family` + - [x] `src` + - [x] `font-stretch` + - [x] `font-style` + - [x] `font-weight` + +Checklist of CSS properties: +- [x] `align-content` +- [x] `align-items` +- [x] `align-self` +- [ ] `all` +- [ ] `animation` +- [ ] `animation-delay` +- [ ] `animation-direction` +- [ ] `animation-duration` +- [ ] `animation-fill-mode` +- [ ] `animation-iteration-count` +- [ ] `animation-name` +- [ ] `animation-play-state` +- [ ] `animation-timing-function` +- [ ] `backface-visibility` +- [ ] `background` +- [ ] `background-attachment` +- [ ] `background-blend-mode` +- [ ] `background-clip` +- [x] `background-color` +- [x] `background-image` +- [x] `background-origin` +- [x] `background-position` +- [x] `background-position-x` +- [x] `background-position-y` +- [ ] `background-repeat` +- [x] `background-size` +- [x] `border` +- [x] `border-bottom` +- [x] `border-bottom-color` +- [x] `border-bottom-left-radius` +- [x] `border-bottom-right-radius` +- [x] `border-bottom-style` +- [x] `border-bottom-width` +- [ ] `border-collapse` +- [x] `border-color` +- [ ] `border-image` +- [ ] `border-image-outset` +- [ ] `border-image-repeat` +- [ ] `border-image-slice` +- [ ] `border-image-source` +- [ ] `border-image-width` +- [x] `border-left` +- [x] `border-left-color` +- [x] `border-left-style` +- [x] `border-left-width` +- [x] `border-radius` +- [x] `border-right` +- [x] `border-right-color` +- [x] `border-right-style` +- [x] `border-right-width` +- [ ] `border-spacing` +- [x] `border-style` +- [x] `border-top` +- [x] `border-top-color` +- [x] `border-top-left-radius` +- [x] `border-top-right-radius` +- [x] `border-top-style` +- [x] `border-top-width` +- [x] `border-width` +- [x] `bottom` +- [ ] `box-decoration-break` +- [x] `box-shadow` +- [ ] `box-sizing` +- [ ] `break-after` +- [ ] `break-before` +- [ ] `break-inside` +- [ ] `caption-side` +- [ ] `caret-color` +- [ ] `@charset` +- [ ] `clear` +- [ ] `clip` +- [ ] `clip-path` +- [x] `color` +- [ ] `column-count` +- [ ] `column-fill` +- [ ] `column-gap` +- [ ] `column-rule` +- [ ] `column-rule-color` +- [ ] `column-rule-style` +- [ ] `column-rule-width` +- [ ] `column-span` +- [ ] `column-width` +- [ ] `columns` +- [ ] `content` +- [ ] `counter-increment` +- [ ] `counter-reset` +- [ ] `cursor` +- [ ] `direction` +- [x] `display` + - [x] `flex` + - [x] `none` + - [x] `block` + - [x] `inline` +- [ ] `empty-cells` +- [ ] `filter` +- [ ] `flex` +- [x] `flex-basis` +- [x] `flex-direction` +- [ ] `flex-flow` +- [x] `flex-grow` +- [x] `flex-shrink` +- [x] `flex-wrap` +- [ ] `float` +- [ ] `font` +- [x] `font-family` +- [ ] `font-feature-settings` +- [ ] `font-kerning` +- [x] `font-size` +- [ ] `font-size-adjust` +- [x] `font-stretch` +- [x] `font-style` +- [ ] `font-variant` +- [ ] `font-variant-caps` +- [x] `font-weight` +- [ ] `grid` +- [ ] `grid-area` +- [ ] `grid-auto-columns` +- [ ] `grid-auto-flow` +- [ ] `grid-auto-rows` +- [ ] `grid-column` +- [ ] `grid-column-end` +- [ ] `grid-column-gap` +- [ ] `grid-column-start` +- [ ] `grid-gap` +- [ ] `grid-row` +- [ ] `grid-row-end` +- [ ] `grid-row-gap` +- [ ] `grid-row-start` +- [ ] `grid-template` +- [ ] `grid-template-areas` +- [ ] `grid-template-columns` +- [ ] `grid-template-rows` +- [ ] `hanging-punctuation` +- [x] `height` +- [ ] `hyphens` +- [ ] `@import` +- [ ] `isolation` +- [x] `justify-content` +- [ ] `@keyframes` +- [x] `left` +- [ ] `letter-spacing` +- [x] `line-height` +- [ ] `list-style` +- [ ] `list-style-image` +- [ ] `list-style-position` +- [ ] `list-style-type` +- [x] `margin` +- [x] `margin-bottom` +- [x] `margin-left` +- [x] `margin-right` +- [x] `margin-top` +- [x] `max-height` +- [x] `max-width` +- [ ] `@media` +- [x] `min-height` +- [x] `min-width` +- [ ] `mix-blend-mode` +- [ ] `object-fit` +- [ ] `object-position` +- [x] `opacity` +- [ ] `order` +- [ ] `outline` +- [ ] `outline-color` +- [ ] `outline-offset` +- [ ] `outline-style` +- [ ] `outline-width` +- [ ] `overflow` +- [ ] `overflow-x` +- [ ] `overflow-y` +- [x] `overflow-wrap` +- [x] `padding` +- [x] `padding-bottom` +- [x] `padding-left` +- [x] `padding-right` +- [x] `padding-top` +- [ ] `page-break-after` +- [ ] `page-break-before` +- [ ] `page-break-inside` +- [ ] `perspective` +- [ ] `perspective-origin` +- [x] `pointer-events` +- [x] `position` + - [x] `absolute` + - [x] `relative` + - [x] `static` + - [ ] `fixed` +- [ ] `quotes` +- [ ] `resize` +- [x] `right` +- [ ] `scroll-behavior` +- [x] `tab-size` +- [ ] `table-layout` +- [x] `text-align` +- [ ] `text-align-last` +- [ ] `text-decoration` +- [ ] `text-decoration-color` +- [ ] `text-decoration-line` +- [ ] `text-decoration-style` +- [ ] `text-indent` +- [ ] `text-justify` +- [ ] `text-overflow` +- [ ] `text-shadow` +- [ ] `text-transform` +- [x] `top` +- [ ] `transform` +- [ ] `transform-origin` +- [ ] `transform-style` +- [ ] `transition` +- [ ] `transition-delay` +- [ ] `transition-duration` +- [ ] `transition-property` +- [ ] `transition-timing-function` +- [ ] `unicode-bidi` +- [ ] `user-select` +- [ ] `vertical-align` +- [ ] `visibility` +- [x] `white-space` +- [x] `width` +- [ ] `word-break` +- [ ] `word-spacing` +- [ ] `word-wrap` +- [ ] `writing-mode` +- [x] `z-index` From 6edc86d589450bd728e221ba8eec0a1072230b68 Mon Sep 17 00:00:00 2001 From: SpinyOwl Date: Tue, 26 May 2026 19:28:55 +0200 Subject: [PATCH 014/121] Implement word break wrapping support --- .../layout/impl/InlineFormattingContext.java | 27 +++- .../spinygui/core/style/ResolvedStyle.java | 10 ++ .../core/style/stylesheet/Properties.java | 2 + .../property/TextPropertyProvider.java | 18 +++ .../spinygui/core/style/types/WordBreak.java | 48 +++++++ .../impl/InlineFormattingContextTest.java | 124 ++++++++++++++++++ .../property/TextPropertyProviderTest.java | 77 +++++++++++ .../spinygui/demo/complex/NvgExample.java | 19 +++ docs/features/css-properties-support.md | 72 +++++++++- 9 files changed, 392 insertions(+), 5 deletions(-) create mode 100644 core/src/main/java/com/spinyowl/spinygui/core/style/types/WordBreak.java create mode 100644 core/src/test/java/com/spinyowl/spinygui/core/style/stylesheet/property/TextPropertyProviderTest.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/layout/impl/InlineFormattingContext.java b/core/src/main/java/com/spinyowl/spinygui/core/layout/impl/InlineFormattingContext.java index 3a0829b5..8860d2fb 100644 --- a/core/src/main/java/com/spinyowl/spinygui/core/layout/impl/InlineFormattingContext.java +++ b/core/src/main/java/com/spinyowl/spinygui/core/layout/impl/InlineFormattingContext.java @@ -13,6 +13,7 @@ import static com.spinyowl.spinygui.core.style.types.WhiteSpace.PRE; import static com.spinyowl.spinygui.core.style.types.WhiteSpace.PRE_LINE; import static com.spinyowl.spinygui.core.style.types.WhiteSpace.PRE_WRAP; +import static com.spinyowl.spinygui.core.style.types.WordBreak.BREAK_ALL; import com.spinyowl.spinygui.core.font.Font; import com.spinyowl.spinygui.core.font.FontStyle; @@ -27,6 +28,7 @@ import com.spinyowl.spinygui.core.style.stylesheet.util.StyleUtils; import com.spinyowl.spinygui.core.style.types.OverflowWrap; import com.spinyowl.spinygui.core.style.types.WhiteSpace; +import com.spinyowl.spinygui.core.style.types.WordBreak; import com.spinyowl.spinygui.core.system.font.TextLineMetrics; import java.util.ArrayList; import java.util.Collection; @@ -245,7 +247,13 @@ private List buildLines( } float width = unit.width(textMeasurer); - boolean wrap = unit.wraps() && !line.empty() && cursorX + width > contentX + availableWidth; + boolean breakIntoCharacters = + unit.breakIntoCharacters(textMeasurer, availableWidth, cursorX, contentX); + boolean wrap = + !breakIntoCharacters + && unit.wraps() + && !line.empty() + && cursorX + width > contentX + availableWidth; if (wrap) { trimTrailingSpace(line); lines.add(closeLine(line)); @@ -257,7 +265,7 @@ private List buildLines( } } - if (unit.breakWord(textMeasurer, availableWidth) && cursorX + width > contentX + availableWidth) { + if (breakIntoCharacters) { for (InlineUnit part : unit.breakIntoCharacters()) { if (!line.empty() && cursorX + part.width(textMeasurer) > contentX + availableWidth) { lines.add(closeLine(line)); @@ -535,12 +543,25 @@ boolean wraps() { boolean breakWord(TextMeasurer measurer, float availableWidth) { OverflowWrap overflowWrap = style.overflowWrap(); - return (BREAK_WORD.equals(overflowWrap) || ANYWHERE.equals(overflowWrap)) + WordBreak wordBreak = style.wordBreak(); + return (BREAK_WORD.equals(overflowWrap) + || ANYWHERE.equals(overflowWrap) + || WordBreak.BREAK_WORD.equals(wordBreak)) && text != null && !space + && wraps() && width(measurer) > availableWidth; } + boolean breakIntoCharacters( + TextMeasurer measurer, float availableWidth, float cursorX, float contentX) { + return text != null + && !space + && wraps() + && (BREAK_ALL.equals(style.wordBreak()) || breakWord(measurer, availableWidth)) + && cursorX + width(measurer) > contentX + availableWidth; + } + List breakIntoCharacters() { var parts = new ArrayList(); for (int i = 0; i < text.length(); i++) { diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/ResolvedStyle.java b/core/src/main/java/com/spinyowl/spinygui/core/style/ResolvedStyle.java index d6d97308..17fbc254 100644 --- a/core/src/main/java/com/spinyowl/spinygui/core/style/ResolvedStyle.java +++ b/core/src/main/java/com/spinyowl/spinygui/core/style/ResolvedStyle.java @@ -65,6 +65,7 @@ import static com.spinyowl.spinygui.core.style.stylesheet.Properties.TOP; import static com.spinyowl.spinygui.core.style.stylesheet.Properties.WHITE_SPACE; import static com.spinyowl.spinygui.core.style.stylesheet.Properties.WIDTH; +import static com.spinyowl.spinygui.core.style.stylesheet.Properties.WORD_BREAK; import static com.spinyowl.spinygui.core.style.stylesheet.Properties.Z_INDEX; import com.spinyowl.spinygui.core.font.FontStyle; @@ -78,6 +79,7 @@ import com.spinyowl.spinygui.core.style.types.Position; import com.spinyowl.spinygui.core.style.types.TextAlign; import com.spinyowl.spinygui.core.style.types.WhiteSpace; +import com.spinyowl.spinygui.core.style.types.WordBreak; import com.spinyowl.spinygui.core.style.types.background.BackgroundOrigin; import com.spinyowl.spinygui.core.style.types.background.BackgroundRepeat; import com.spinyowl.spinygui.core.style.types.background.BackgroundSize; @@ -718,4 +720,12 @@ public OverflowWrap overflowWrap() { public void overflowWrap(OverflowWrap overflowWrap) { set(OVERFLOW_WRAP, overflowWrap); } + + public WordBreak wordBreak() { + return get(WORD_BREAK); + } + + public void wordBreak(WordBreak wordBreak) { + set(WORD_BREAK, wordBreak); + } } diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/Properties.java b/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/Properties.java index f668e5d0..47bdef52 100644 --- a/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/Properties.java +++ b/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/Properties.java @@ -114,4 +114,6 @@ public final class Properties { public static final String OVERFLOW_X = "overflow-x"; public static final String OVERFLOW_Y = "overflow-y"; public static final String OVERFLOW_WRAP = "overflow-wrap"; + public static final String WORD_BREAK = "word-break"; + public static final String WORD_WRAP = "word-wrap"; } diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/TextPropertyProvider.java b/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/TextPropertyProvider.java index 3f048036..99e3e00a 100644 --- a/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/TextPropertyProvider.java +++ b/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/TextPropertyProvider.java @@ -2,6 +2,8 @@ import static com.spinyowl.spinygui.core.style.stylesheet.Properties.OVERFLOW_WRAP; import static com.spinyowl.spinygui.core.style.stylesheet.Properties.TEXT_ALIGN; +import static com.spinyowl.spinygui.core.style.stylesheet.Properties.WORD_BREAK; +import static com.spinyowl.spinygui.core.style.stylesheet.Properties.WORD_WRAP; import static com.spinyowl.spinygui.core.style.stylesheet.Property.checkValue; import static com.spinyowl.spinygui.core.style.stylesheet.Property.put; @@ -10,6 +12,7 @@ import com.spinyowl.spinygui.core.style.stylesheet.term.TermIdent; import com.spinyowl.spinygui.core.style.types.OverflowWrap; import com.spinyowl.spinygui.core.style.types.TextAlign; +import com.spinyowl.spinygui.core.style.types.WordBreak; import java.util.List; public class TextPropertyProvider implements PropertyProvider { @@ -30,6 +33,21 @@ public List getProperties() { .inheritable(true) .updater(put(OVERFLOW_WRAP, TermIdent.class, OverflowWrap::find)) .validator(checkValue(TermIdent.class, OverflowWrap::contains)) + .build(), + Property.builder() + .name(WORD_BREAK) + .defaultValue(new TermIdent(WordBreak.NORMAL.name())) + .inheritable(true) + .updater(put(WORD_BREAK, TermIdent.class, WordBreak::find)) + .validator(checkValue(TermIdent.class, WordBreak::contains)) + .build(), + Property.builder() + .name(WORD_WRAP) + .defaultValue(new TermIdent(OverflowWrap.NORMAL.name())) + .inheritable(true) + .updater(put(OVERFLOW_WRAP, TermIdent.class, OverflowWrap::find)) + .validator(checkValue(TermIdent.class, OverflowWrap::contains)) + .shorthand(true) .build()); } } diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/types/WordBreak.java b/core/src/main/java/com/spinyowl/spinygui/core/style/types/WordBreak.java new file mode 100644 index 00000000..2469f0cf --- /dev/null +++ b/core/src/main/java/com/spinyowl/spinygui/core/style/types/WordBreak.java @@ -0,0 +1,48 @@ +package com.spinyowl.spinygui.core.style.types; + +import static lombok.AccessLevel.PRIVATE; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NonNull; + +/** CSS word-break. */ +@Getter +@EqualsAndHashCode +@AllArgsConstructor(access = PRIVATE) +public final class WordBreak { + + private static final Map VALUES = new HashMap<>(); + + public static final WordBreak NORMAL = WordBreak.create("normal"); + public static final WordBreak BREAK_ALL = WordBreak.create("break-all"); + public static final WordBreak KEEP_ALL = WordBreak.create("keep-all"); + public static final WordBreak BREAK_WORD = WordBreak.create("break-word"); + + @NonNull private final String name; + + public static WordBreak create(@NonNull String name) { + return VALUES.computeIfAbsent(name.toLowerCase(), WordBreak::new); + } + + public static WordBreak find(@NonNull String name) { + return VALUES.get(name.toLowerCase()); + } + + public static Set values() { + return Set.copyOf(VALUES.values()); + } + + public static boolean contains(@NonNull String name) { + return VALUES.containsKey(name.toLowerCase()); + } + + @Override + public String toString() { + return name; + } +} diff --git a/core/src/test/java/com/spinyowl/spinygui/core/layout/impl/InlineFormattingContextTest.java b/core/src/test/java/com/spinyowl/spinygui/core/layout/impl/InlineFormattingContextTest.java index fe033107..30e30f9b 100644 --- a/core/src/test/java/com/spinyowl/spinygui/core/layout/impl/InlineFormattingContextTest.java +++ b/core/src/test/java/com/spinyowl/spinygui/core/layout/impl/InlineFormattingContextTest.java @@ -1,5 +1,6 @@ package com.spinyowl.spinygui.core.layout.impl; +import static com.spinyowl.spinygui.core.style.stylesheet.Properties.WORD_WRAP; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -11,12 +12,16 @@ import com.spinyowl.spinygui.core.node.NodeBuilder; import com.spinyowl.spinygui.core.node.Text; import com.spinyowl.spinygui.core.style.ResolvedStyle; +import com.spinyowl.spinygui.core.style.stylesheet.Property; +import com.spinyowl.spinygui.core.style.stylesheet.property.TextPropertyProvider; +import com.spinyowl.spinygui.core.style.stylesheet.term.TermIdent; import com.spinyowl.spinygui.core.style.types.Color; import com.spinyowl.spinygui.core.style.types.Display; import com.spinyowl.spinygui.core.style.types.OverflowWrap; import com.spinyowl.spinygui.core.style.types.Position; import com.spinyowl.spinygui.core.style.types.TextAlign; import com.spinyowl.spinygui.core.style.types.WhiteSpace; +import com.spinyowl.spinygui.core.style.types.WordBreak; import com.spinyowl.spinygui.core.style.types.border.BorderStyle; import com.spinyowl.spinygui.core.style.types.length.Length; import com.spinyowl.spinygui.core.system.font.FontMetrics; @@ -84,6 +89,115 @@ void layout_whenOverflowWrapBreakWord_breaksLongWord() { assertEquals(10, text.inlineFragments().get(5).y()); } + @Test + void layout_whenWordWrapBreakWord_breaksLongWord() { + applyStyleProperty(parent, WORD_WRAP, "break-word"); + Text text = NodeBuilder.text("abcdef"); + parent.addChild(text); + + float height = formattingContext.layout(parent, List.of(text), 0); + + assertEquals(20, height); + assertEquals(6, text.inlineFragments().size()); + assertEquals("a", text.inlineFragments().get(0).text()); + assertEquals("f", text.inlineFragments().get(5).text()); + assertEquals(10, text.inlineFragments().get(5).y()); + } + + @Test + void layout_whenWordWrapAnywhere_breaksLongWord() { + applyStyleProperty(parent, WORD_WRAP, "anywhere"); + Text text = NodeBuilder.text("abcdef"); + parent.addChild(text); + + float height = formattingContext.layout(parent, List.of(text), 0); + + assertEquals(20, height); + assertEquals(6, text.inlineFragments().size()); + assertEquals("a", text.inlineFragments().get(0).text()); + assertEquals("f", text.inlineFragments().get(5).text()); + assertEquals(10, text.inlineFragments().get(5).y()); + } + + @Test + void layout_whenWordBreakBreakWord_breaksLongWord() { + parent.resolvedStyle().wordBreak(WordBreak.BREAK_WORD); + Text text = NodeBuilder.text("abcdef"); + parent.addChild(text); + + float height = formattingContext.layout(parent, List.of(text), 0); + + assertEquals(20, height); + assertEquals(6, text.inlineFragments().size()); + assertEquals("a", text.inlineFragments().get(0).text()); + assertEquals("f", text.inlineFragments().get(5).text()); + assertEquals(10, text.inlineFragments().get(5).y()); + } + + @Test + void layout_whenWordBreakBreakAll_usesRemainingLineSpace() { + Text text = NodeBuilder.text("abc def"); + parent.resolvedStyle().wordBreak(WordBreak.BREAK_ALL); + parent.addChild(text); + + float height = formattingContext.layout(parent, List.of(text), 0); + + assertEquals(20, height); + assertEquals("abc", text.inlineFragments().get(0).text()); + assertEquals(" ", text.inlineFragments().get(1).text()); + assertEquals("d", text.inlineFragments().get(2).text()); + assertEquals(40, text.inlineFragments().get(2).x()); + assertEquals("e", text.inlineFragments().get(3).text()); + assertEquals(10, text.inlineFragments().get(3).y()); + } + + @Test + void layout_whenWordBreakNormal_preservesSpaceBasedWrapping() { + Text text = NodeBuilder.text("abc def"); + parent.resolvedStyle().wordBreak(WordBreak.NORMAL); + parent.addChild(text); + + float height = formattingContext.layout(parent, List.of(text), 0); + + assertEquals(20, height); + assertEquals(2, text.inlineFragments().size()); + assertEquals("abc", text.inlineFragments().get(0).text()); + assertEquals("def", text.inlineFragments().get(1).text()); + assertEquals(0, text.inlineFragments().get(1).x()); + } + + @Test + void layout_whenWordBreakKeepAll_behavesLikeNormal() { + Text text = NodeBuilder.text("abc def"); + parent.resolvedStyle().wordBreak(WordBreak.KEEP_ALL); + parent.addChild(text); + + float height = formattingContext.layout(parent, List.of(text), 0); + + assertEquals(20, height); + assertEquals(2, text.inlineFragments().size()); + assertEquals("abc", text.inlineFragments().get(0).text()); + assertEquals("def", text.inlineFragments().get(1).text()); + assertEquals(0, text.inlineFragments().get(1).x()); + } + + @Test + void layout_whenWhiteSpaceNowrap_ignoresWordBreakBreakAll() { + Text text = NodeBuilder.text("abc def"); + parent.resolvedStyle().whiteSpace(WhiteSpace.NOWRAP); + parent.resolvedStyle().wordBreak(WordBreak.BREAK_ALL); + parent.addChild(text); + + float height = formattingContext.layout(parent, List.of(text), 0); + + assertEquals(10, height); + assertEquals(3, text.inlineFragments().size()); + assertEquals("abc", text.inlineFragments().get(0).text()); + assertEquals(" ", text.inlineFragments().get(1).text()); + assertEquals("def", text.inlineFragments().get(2).text()); + assertEquals(40, text.inlineFragments().get(2).x()); + } + @Test void layout_whenConsecutiveTextNodes_shareLineBox() { parent.box().content().width(200); @@ -292,9 +406,19 @@ private void style(Element element, Display display) { style.whiteSpace(WhiteSpace.NORMAL); style.textAlign(TextAlign.LEFT); style.overflowWrap(OverflowWrap.NORMAL); + style.wordBreak(WordBreak.NORMAL); style.tabSize(4); } + private void applyStyleProperty(Element element, String name, String value) { + Property property = + new TextPropertyProvider().getProperties().stream() + .filter(candidate -> name.equals(candidate.getName())) + .findFirst() + .orElseThrow(); + property.apply(element, new TermIdent(value)); + } + private static class FixedTextMeasurer implements TextMeasurer { private int measurementCount; diff --git a/core/src/test/java/com/spinyowl/spinygui/core/style/stylesheet/property/TextPropertyProviderTest.java b/core/src/test/java/com/spinyowl/spinygui/core/style/stylesheet/property/TextPropertyProviderTest.java new file mode 100644 index 00000000..f5de04c5 --- /dev/null +++ b/core/src/test/java/com/spinyowl/spinygui/core/style/stylesheet/property/TextPropertyProviderTest.java @@ -0,0 +1,77 @@ +package com.spinyowl.spinygui.core.style.stylesheet.property; + +import static com.spinyowl.spinygui.core.style.stylesheet.Properties.OVERFLOW_WRAP; +import static com.spinyowl.spinygui.core.style.stylesheet.Properties.WORD_BREAK; +import static com.spinyowl.spinygui.core.style.stylesheet.Properties.WORD_WRAP; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.spinyowl.spinygui.core.node.Element; +import com.spinyowl.spinygui.core.node.NodeBuilder; +import com.spinyowl.spinygui.core.style.stylesheet.Property; +import com.spinyowl.spinygui.core.style.stylesheet.term.TermIdent; +import com.spinyowl.spinygui.core.style.types.OverflowWrap; +import com.spinyowl.spinygui.core.style.types.WordBreak; +import org.junit.jupiter.api.Test; + +class TextPropertyProviderTest { + + @Test + void wordBreak_acceptsSupportedValues() { + Property property = property(WORD_BREAK); + Element element = NodeBuilder.div(); + + property.apply(element, new TermIdent("break-all")); + + assertEquals(WordBreak.BREAK_ALL, element.resolvedStyle().wordBreak()); + } + + @Test + void wordBreak_rejectsUnsupportedValues() { + Property property = property(WORD_BREAK); + Element element = NodeBuilder.div(); + + property.apply(element, new TermIdent("invalid")); + + assertNull(element.resolvedStyle().wordBreak()); + } + + @Test + void wordWrap_updatesOverflowWrap() { + Property property = property(WORD_WRAP); + Element element = NodeBuilder.div(); + + property.apply(element, new TermIdent("break-word")); + + assertEquals(OverflowWrap.BREAK_WORD, element.resolvedStyle().overflowWrap()); + assertNull(element.resolvedStyle().getSafe(WORD_WRAP)); + } + + @Test + void wordWrap_acceptsOverflowWrapValues() { + Property property = property(WORD_WRAP); + + assertTrue(property.getValidator().test(new TermIdent("normal"))); + assertTrue(property.getValidator().test(new TermIdent("break-word"))); + assertTrue(property.getValidator().test(new TermIdent("anywhere"))); + assertFalse(property.getValidator().test(new TermIdent("break-all"))); + } + + @Test + void wordWrap_doesNotReplaceOverflowWrapPropertyRegistration() { + Property overflowWrap = property(OVERFLOW_WRAP); + Property wordWrap = property(WORD_WRAP); + + assertEquals(OVERFLOW_WRAP, overflowWrap.getName()); + assertEquals(WORD_WRAP, wordWrap.getName()); + } + + private Property property(String name) { + return new TextPropertyProvider().getProperties().stream() + .filter(property -> name.equals(property.getName())) + .findFirst() + .orElseThrow(); + } +} diff --git a/demo.complex/src/main/java/com/spinyowl/spinygui/demo/complex/NvgExample.java b/demo.complex/src/main/java/com/spinyowl/spinygui/demo/complex/NvgExample.java index 4c95036c..4fd1bb3f 100644 --- a/demo.complex/src/main/java/com/spinyowl/spinygui/demo/complex/NvgExample.java +++ b/demo.complex/src/main/java/com/spinyowl/spinygui/demo/complex/NvgExample.java @@ -90,6 +90,19 @@ private String getFlexCss() { #t2 { width: 500px; } +.wrap-demo { + display: block; + width: 120px; + font-size: 16px; + border-color: #45AAFF; +} +.word-wrap-demo { + word-wrap: break-word; +} +.word-break-demo { + word-break: break-all; + border-color: #AA45FF; +} """; } @@ -108,6 +121,12 @@ private String getXml() { Hello world. Lorem ipsum dolor.s Hello World +

+ word-wrap: Supercalifragilisticexpialidocious +
+
+ word-break: Supercalifragilisticexpialidocious +
diff --git a/docs/features/css-properties-support.md b/docs/features/css-properties-support.md index dd1c22ef..64779595 100644 --- a/docs/features/css-properties-support.md +++ b/docs/features/css-properties-support.md @@ -1,6 +1,74 @@ Supported means the property is registered by a `PropertyProvider` in `core.style.stylesheet.property` and can be parsed/applied through the stylesheet property store. +Estimate scale for unchecked entries: +- `XS`: property provider/type/accessor only; no new layout or renderer path. +- `S`: small parser/style addition plus a localized existing layout or renderer hook. +- `M`: several coordinated changes across property parsing, resolved style, layout/rendering, and tests. +- `L`: new layout/rendering behavior or cross-cutting event/hit-test integration. +- `XL`: subsystem-level work; best implemented as a feature family, not property by property. +- `N/A`: browser CSS feature outside the current GUI model unless a missing subsystem is introduced. + +Approximate implementation estimates for unchecked entries: + +| Property | Estimate | Main work | +| --- | --- | --- | +| `all` | M | Cascade-wide reset/inherit/revert behavior across registered properties. | +| `animation`, `animation-delay`, `animation-direction`, `animation-duration`, `animation-fill-mode`, `animation-iteration-count`, `animation-name`, `animation-play-state`, `animation-timing-function`, `@keyframes` | XL | Animation timeline, keyframe model, interpolation, invalidation, and render/layout integration. | +| `backface-visibility` | L | Depends on transform/3D scene semantics that are not present. | +| `background` | M | Shorthand parser that expands current background color/image/position/size/origin plus future repeat/clip/attachment. | +| `background-attachment` | M | Requires scroll/viewport semantics for fixed/local behavior. | +| `background-blend-mode` | L | Renderer blend/compositing support. | +| `background-clip` | S | Clip target selection for existing background renderer. | +| `background-repeat` | S | Type already exists; needs provider plus image tiling in renderer. | +| `border-collapse`, `border-spacing`, `caption-side`, `empty-cells`, `table-layout` | XL | Requires table layout model, table boxes, border conflict handling, and renderer support. | +| `border-image`, `border-image-outset`, `border-image-repeat`, `border-image-slice`, `border-image-source`, `border-image-width` | L | Image border slicing, scaling/repeating, and border renderer changes. | +| `box-decoration-break` | M | Inline fragment background/border painting policy. | +| `box-sizing` | M | Sizing calculations must support content-box and border-box consistently in block/flex/inline layout. | +| `break-after`, `break-before`, `break-inside`, `page-break-after`, `page-break-before`, `page-break-inside` | N/A | Fragmentation/paged layout is not currently modeled. | +| `caret-color` | M | Needs caret styling in text input/caret rendering path. | +| `@charset` | XS | Parser-level metadata handling; little runtime effect if source is already decoded. | +| `clear`, `float` | L | Requires float layout and block formatting interaction. | +| `clip` | M | Absolute-position clipping plus renderer and hit-test clipping. | +| `clip-path` | L | Shape/path clipping in renderer and hit-testing. | +| `column-count`, `column-fill`, `column-gap`, `column-rule`, `column-rule-color`, `column-rule-style`, `column-rule-width`, `column-span`, `column-width`, `columns` | XL | Multi-column layout, fragmentation, balancing, and column rule painting. | +| `content`, `counter-increment`, `counter-reset`, `quotes` | L | Generated content, pseudo-elements, and counter state. | +| `cursor` | M | Property parsing plus wiring resolved style to cursor service during hover/focus transitions. | +| `direction`, `unicode-bidi`, `writing-mode` | XL | Bidirectional and vertical text shaping/layout support. | +| `filter` | L | Renderer effects pipeline for blur/color/filter operations. | +| `flex` | S | Shorthand expansion into `flex-grow`, `flex-shrink`, and `flex-basis`. | +| `flex-flow` | XS | Shorthand expansion into `flex-direction` and `flex-wrap`. | +| `font` | M | CSS font shorthand parser with optional style/variant/weight/stretch/size/line-height/family handling. | +| `font-feature-settings`, `font-kerning`, `font-variant`, `font-variant-caps` | L | Font shaping/OpenType feature support through font service and NanoVG/text backend. | +| `font-size-adjust` | M | Font metrics integration and adjusted used font-size calculations. | +| `grid`, `grid-area`, `grid-auto-columns`, `grid-auto-flow`, `grid-auto-rows`, `grid-column`, `grid-column-end`, `grid-column-gap`, `grid-column-start`, `grid-gap`, `grid-row`, `grid-row-end`, `grid-row-gap`, `grid-row-start`, `grid-template`, `grid-template-areas`, `grid-template-columns`, `grid-template-rows` | XL | Full grid formatting context, track sizing, placement, gaps, and tests. | +| `hanging-punctuation` | L | Text layout punctuation positioning support. | +| `hyphens` | L | Language-aware hyphenation and line breaking. | +| `@import` | M | Stylesheet loading, URL/resource resolution, cycle/error handling, and cascade ordering. | +| `isolation`, `mix-blend-mode` | L | Stacking context and compositing support. | +| `letter-spacing`, `word-spacing` | M | Text measurement and renderer glyph positioning changes. | +| `list-style`, `list-style-image`, `list-style-position`, `list-style-type` | L | List item display model, marker layout, and marker rendering. | +| `@media` | L | Media query evaluation, environment model, and dynamic stylesheet invalidation. | +| `object-fit`, `object-position` | M | Replaced element/image sizing and drawing behavior. | +| `order` | M | Flex item ordering plus layout invalidation and traversal implications. | +| `outline`, `outline-color`, `outline-offset`, `outline-style`, `outline-width` | M | Outline property parsing and paint path outside border box. | +| `overflow`, `overflow-x`, `overflow-y` | L | Clipping, scroll container behavior, hit-testing, and possibly scroll input. | +| `perspective`, `perspective-origin`, `transform`, `transform-origin`, `transform-style` | XL | Transform matrices, coordinate conversion, stacking contexts, renderer transforms, and hit-testing. | +| `position: fixed` | L | Viewport-relative containing block, scroll behavior, stacking, and event coordinate handling. | +| `resize` | L | User interaction, constraints, layout invalidation, and cursor behavior. | +| `scroll-behavior` | M | Scroll containers first, then animated scroll behavior. | +| `text-align-last` | M | Inline layout line-final alignment logic. | +| `text-decoration`, `text-decoration-color`, `text-decoration-line`, `text-decoration-style` | M | Text decoration model plus underline/overline/strike rendering. | +| `text-indent` | M | First-line inline layout offset. | +| `text-justify` | L | Text justification algorithms and spacing distribution. | +| `text-overflow` | M | Overflow clipping plus ellipsis measurement/rendering. | +| `text-shadow` | S | Similar to `box-shadow`, localized to text renderer. | +| `text-transform` | S | Text preprocessing before measurement/rendering; locale-sensitive cases may raise this to M. | +| `transition`, `transition-delay`, `transition-duration`, `transition-property`, `transition-timing-function` | XL | Style change tracking, animation timeline, interpolation, invalidation. | +| `user-select` | M | Selection model and input behavior integration. | +| `vertical-align` | M | Inline formatting baseline/alignment behavior; type classes partly exist. | +| `visibility` | M | Paint suppression while preserving layout, plus event/hit-test decisions. | + At-rules with dedicated parser support: - [x] `@font-face` - [x] `font-family` @@ -228,8 +296,8 @@ Checklist of CSS properties: - [ ] `visibility` - [x] `white-space` - [x] `width` -- [ ] `word-break` +- [x] `word-break` - [ ] `word-spacing` -- [ ] `word-wrap` +- [x] `word-wrap` - [ ] `writing-mode` - [x] `z-index` From 12db4d9f088e283abc1a2cdb1623facfddad042f Mon Sep 17 00:00:00 2001 From: SpinyOwl Date: Tue, 26 May 2026 19:35:15 +0200 Subject: [PATCH 015/121] Add GUI test HTML app --- gui-test-html-app/app.cmd | 26 ++++++++++ gui-test-html-app/app.html | 95 ++++++++++++++++++++++++++++++++++++ gui-test-html-app/layout.xml | 28 +++++++++++ gui-test-html-app/styles.css | 14 ++++++ 4 files changed, 163 insertions(+) create mode 100644 gui-test-html-app/app.cmd create mode 100644 gui-test-html-app/app.html create mode 100644 gui-test-html-app/layout.xml create mode 100644 gui-test-html-app/styles.css diff --git a/gui-test-html-app/app.cmd b/gui-test-html-app/app.cmd new file mode 100644 index 00000000..7b1ba78f --- /dev/null +++ b/gui-test-html-app/app.cmd @@ -0,0 +1,26 @@ +@echo off +if defined CHROME if exist "%CHROME%" goto chrome_found + +set "CHROME=%ProgramFiles%\Google\Chrome\Application\chrome.exe" +if exist "%CHROME%" goto chrome_found + +set "CHROME=%ProgramFiles(x86)%\Google\Chrome\Application\chrome.exe" +if exist "%CHROME%" goto chrome_found + +set "CHROME=%LocalAppData%\Google\Chrome\Application\chrome.exe" +if exist "%CHROME%" goto chrome_found + +for /f "delims=" %%I in ('where chrome 2^>nul') do ( + set "CHROME=%%I" + goto chrome_found +) + +echo Chrome executable was not found. Set CHROME to chrome.exe path and run again. +exit /b 1 + +:chrome_found +set CHROME_OPTS=--chrome-frame --window-position=250,50 --window-size="415,438" +set "WD=%cd%" +set APP=--app="%WD%\app.html" + +start "" "%CHROME%" %CHROME_OPTS% %APP% diff --git a/gui-test-html-app/app.html b/gui-test-html-app/app.html new file mode 100644 index 00000000..152f30d0 --- /dev/null +++ b/gui-test-html-app/app.html @@ -0,0 +1,95 @@ + + + + + + + + +
+
c1 +
c11 Cfjg
+
c12
+
+
+ Hello world. Lorem ipsum dolor. + Hello world. Lorem ipsum dolor. + Hello world. Lorem ipsum dolor.s + Hello World +
+ +
+
+
+ Hello world. Lorem ipsum dolor. + Hello world A. Lorem ipsum dolor. + Hello world B. Lorem ipsum dolor. +
+
+
+
+
s1
+
s2
+
+
+
+ + + \ No newline at end of file diff --git a/gui-test-html-app/layout.xml b/gui-test-html-app/layout.xml new file mode 100644 index 00000000..033d9266 --- /dev/null +++ b/gui-test-html-app/layout.xml @@ -0,0 +1,28 @@ + +
+
c1 +
c11 Cfjg
+
c12
+
+
+ Hello world. Lorem ipsum dolor. + Hello world. Lorem ipsum dolor. + Hello world. Lorem ipsum dolor.s + Hello World +
+ +
+
+
+ Hello world. Lorem ipsum dolor. Hello world A. Lorem ipsum dolor. Hello world. Lorem ipsum dolor. Hello world A. Lorem ipsum dolor. +
Hello world B. Lorem ipsum dolor.
+ Hello world. Lorem ipsum dolor. Hello world A. Lorem ipsum dolor. Hello world. Lorem ipsum dolor. Hello world A. Lorem ipsum dolor. +
+
+
+
+
s1
+
s2
+
+
+
\ No newline at end of file diff --git a/gui-test-html-app/styles.css b/gui-test-html-app/styles.css new file mode 100644 index 00000000..2eb84ef8 --- /dev/null +++ b/gui-test-html-app/styles.css @@ -0,0 +1,14 @@ + +winframe, +winframe * { + border: 8px solid #8c8c8c; + box-sizing: border-box; /* default behaviour */ + overflow: hidden; /* default behaviour */ +} +winframe:hover, +winframe *:hover { + border: 8px solid #fc3131; +} +#t2 { + width: 500px; +} \ No newline at end of file From f8a9693c9ce4f2e444729e5a75f2caa430116074 Mon Sep 17 00:00:00 2001 From: SpinyOwl Date: Tue, 26 May 2026 21:27:25 +0200 Subject: [PATCH 016/121] Rename Gradle modules to spinygui coordinates --- build.gradle.kts | 4 ++++ settings.gradle.kts | 10 +++++----- .../build.gradle.kts | 4 ++-- .../renderer/lwjgl/nanovg/NvgBorderRenderer.java | 0 .../renderer/lwjgl/nanovg/NvgElementRenderer.java | 0 .../backend/renderer/lwjgl/nanovg/NvgRenderer.java | 0 .../renderer/lwjgl/nanovg/NvgTextRenderer.java | 0 .../renderer/lwjgl/nanovg/util/NvgColorUtil.java | 0 .../renderer/lwjgl/nanovg/util/NvgRenderUtils.java | 0 .../renderer/lwjgl/nanovg/util/NvgShapes.java | 0 .../src/main/java/module-info.java | 0 .../build.gradle.kts | 2 +- .../spinygui/core/backend/renderer/Renderer.java | 0 .../src/main/java/module-info.java | 0 {core => spinygui.core}/.gitignore | 0 {core => spinygui.core}/build.gradle.kts | 0 .../spinygui/core/parser/impl/css/antlr/CSS3.g4 | 0 .../com/spinyowl/spinygui/core/Configuration.java | 0 .../spinyowl/spinygui/core/animation/Animation.java | 0 .../spinyowl/spinygui/core/animation/Animator.java | 0 .../spinygui/core/animation/AnimatorImpl.java | 0 .../spinyowl/spinygui/core/clipboard/Clipboard.java | 0 .../com/spinyowl/spinygui/core/cursor/Cursor.java | 0 .../spinyowl/spinygui/core/cursor/CursorMod.java | 0 .../spinygui/core/cursor/CursorService.java | 0 .../java/com/spinyowl/spinygui/core/event/.gitkeep | 0 .../spinygui/core/event/ChangePositionEvent.java | 0 .../spinygui/core/event/ChangeSizeEvent.java | 0 .../spinygui/core/event/ChangeTextEvent.java | 0 .../com/spinyowl/spinygui/core/event/CharEvent.java | 0 .../spinygui/core/event/CursorEnterEvent.java | 0 .../spinygui/core/event/CursorExitEvent.java | 0 .../com/spinyowl/spinygui/core/event/Event.java | 0 .../spinyowl/spinygui/core/event/EventTarget.java | 0 .../spinyowl/spinygui/core/event/FileDropEvent.java | 0 .../spinyowl/spinygui/core/event/FocusInEvent.java | 0 .../spinyowl/spinygui/core/event/FocusOutEvent.java | 0 .../spinyowl/spinygui/core/event/KeyboardEvent.java | 0 .../spinygui/core/event/MouseClickEvent.java | 0 .../spinygui/core/event/MouseDragEvent.java | 0 .../spinyowl/spinygui/core/event/ScrollEvent.java | 0 .../spinygui/core/event/WindowCloseEvent.java | 0 .../spinygui/core/event/WindowFocusEvent.java | 0 .../spinygui/core/event/WindowIconifyEvent.java | 0 .../spinygui/core/event/WindowPosEvent.java | 0 .../spinygui/core/event/WindowRefreshEvent.java | 0 .../spinygui/core/event/WindowSizeEvent.java | 0 .../spinygui/core/event/listener/EventListener.java | 0 .../core/event/processor/DefaultEventProcessor.java | 0 .../core/event/processor/EventProcessor.java | 0 .../java/com/spinyowl/spinygui/core/font/Font.java | 0 .../com/spinyowl/spinygui/core/font/FontSize.java | 0 .../spinyowl/spinygui/core/font/FontStretch.java | 0 .../com/spinyowl/spinygui/core/font/FontStyle.java | 0 .../com/spinyowl/spinygui/core/font/FontWeight.java | 0 .../com/spinyowl/spinygui/core/image/Image.java | 0 .../com/spinyowl/spinygui/core/input/KeyAction.java | 0 .../com/spinyowl/spinygui/core/input/KeyCode.java | 0 .../com/spinyowl/spinygui/core/input/KeyMod.java | 0 .../com/spinyowl/spinygui/core/input/Keyboard.java | 0 .../spinyowl/spinygui/core/input/KeyboardKey.java | 0 .../spinygui/core/input/KeyboardLayout.java | 0 .../spinyowl/spinygui/core/input/MouseButton.java | 0 .../spinyowl/spinygui/core/input/MouseService.java | 0 .../com/spinyowl/spinygui/core/input/Shortcut.java | 0 .../spinygui/core/input/ShortcutRegistry.java | 0 .../core/input/impl/KeyboardLayoutImpl.java | 0 .../spinygui/core/input/impl/MouseServiceImpl.java | 0 .../core/input/impl/ShortcutRegistryImpl.java | 0 .../java/com/spinyowl/spinygui/core/layout/.gitkeep | 0 .../spinygui/core/layout/ElementLayout.java | 0 .../spinygui/core/layout/InlineFragment.java | 0 .../com/spinyowl/spinygui/core/layout/Layout.java | 0 .../spinygui/core/layout/LayoutContext.java | 0 .../spinygui/core/layout/LayoutService.java | 0 .../com/spinyowl/spinygui/core/layout/LineBox.java | 0 .../spinyowl/spinygui/core/layout/TextLayout.java | 0 .../spinyowl/spinygui/core/layout/TextMeasurer.java | 0 .../spinygui/core/layout/impl/BlockLayout.java | 0 .../spinygui/core/layout/impl/FlexLayout.java | 0 .../core/layout/impl/FontServiceTextMeasurer.java | 0 .../core/layout/impl/InlineFormattingContext.java | 0 .../core/layout/impl/LayoutServiceImpl.java | 0 .../core/layout/impl/LayoutServiceProvider.java | 0 .../spinygui/core/layout/impl/LayoutUtils.java | 0 .../spinygui/core/layout/impl/NoneLayout.java | 0 .../spinygui/core/layout/impl/TextLayoutImpl.java | 0 .../com/spinyowl/spinygui/core/node/Element.java | 0 .../spinyowl/spinygui/core/node/EmptyElement.java | 0 .../java/com/spinyowl/spinygui/core/node/Frame.java | 0 .../java/com/spinyowl/spinygui/core/node/Node.java | 0 .../spinyowl/spinygui/core/node/NodeBuilder.java | 0 .../java/com/spinyowl/spinygui/core/node/Text.java | 0 .../core/node/intersection/Intersection.java | 0 .../core/node/intersection/Intersections.java | 0 .../node/intersection/RectangleIntersection.java | 0 .../com/spinyowl/spinygui/core/node/layout/Box.java | 0 .../spinyowl/spinygui/core/node/layout/Edges.java | 0 .../spinyowl/spinygui/core/node/layout/Rect.java | 0 .../spinyowl/spinygui/core/parser/NodeParser.java | 0 .../spinygui/core/parser/StyleSheetParser.java | 0 .../core/parser/impl/DefaultNodeParser.java | 0 .../core/parser/impl/DefaultStyleSheetParser.java | 0 .../spinygui/core/parser/impl/ParseException.java | 0 .../core/parser/impl/StyleSheetParserFactory.java | 0 .../spinygui/core/parser/impl/css/antlr/CSS3.interp | 0 .../spinygui/core/parser/impl/css/antlr/CSS3.tokens | 0 .../parser/impl/css/antlr/CSS3BaseListener.java | 0 .../core/parser/impl/css/antlr/CSS3BaseVisitor.java | 0 .../core/parser/impl/css/antlr/CSS3Lexer.interp | 0 .../core/parser/impl/css/antlr/CSS3Lexer.java | 0 .../core/parser/impl/css/antlr/CSS3Lexer.tokens | 0 .../core/parser/impl/css/antlr/CSS3Listener.java | 0 .../core/parser/impl/css/antlr/CSS3Parser.java | 0 .../core/parser/impl/css/antlr/CSS3Visitor.java | 0 .../core/parser/impl/css/visitor/AtRuleVisitor.java | 0 .../impl/css/visitor/DeclarationListVisitor.java | 0 .../parser/impl/css/visitor/DeclarationVisitor.java | 0 .../impl/css/visitor/PropertyValueVisitor.java | 0 .../parser/impl/css/visitor/RulesetVisitor.java | 0 .../impl/css/visitor/SelectorGroupVisitor.java | 0 .../parser/impl/css/visitor/SelectorVisitor.java | 0 .../parser/impl/css/visitor/StyleSheetVisitor.java | 0 .../spinyowl/spinygui/core/style/ResolvedStyle.java | 0 .../spinygui/core/style/manager/StyleManager.java | 0 .../core/style/manager/StyleManagerImpl.java | 0 .../spinygui/core/style/stylesheet/AtRule.java | 0 .../spinygui/core/style/stylesheet/Declaration.java | 0 .../spinygui/core/style/stylesheet/Properties.java | 0 .../core/style/stylesheet/PropertiesScanner.java | 0 .../spinygui/core/style/stylesheet/Property.java | 0 .../core/style/stylesheet/PropertyProvider.java | 0 .../core/style/stylesheet/PropertyStore.java | 0 .../style/stylesheet/PropertyStoreProvider.java | 0 .../spinygui/core/style/stylesheet/Ruleset.java | 0 .../spinygui/core/style/stylesheet/Specificity.java | 0 .../spinygui/core/style/stylesheet/StyleSheet.java | 0 .../spinygui/core/style/stylesheet/Term.java | 0 .../core/style/stylesheet/annotation/Priority.java | 0 .../core/style/stylesheet/atrule/FontFaceRule.java | 0 .../style/stylesheet/impl/DefaultPropertyStore.java | 0 .../impl/DefaultPropertyStoreProvider.java | 0 .../property/BackgroundPropertyProvider.java | 0 .../property/BorderColorPropertyProvider.java | 0 .../stylesheet/property/BorderPropertyProvider.java | 0 .../property/BorderRadiusPropertyProvider.java | 0 .../property/BorderStylePropertyProvider.java | 0 .../property/BorderWidthPropertyProvider.java | 0 .../property/BoxShadowPropertyProvider.java | 0 .../stylesheet/property/ColorPropertyProvider.java | 0 .../property/DimensionPropertyProvider.java | 0 .../property/DisplayPropertyProvider.java | 0 .../stylesheet/property/FlexPropertyProvider.java | 0 .../stylesheet/property/FontPropertyProvider.java | 0 .../stylesheet/property/MarginPropertyProvider.java | 0 .../property/OpacityPropertyProvider.java | 0 .../property/PaddingPropertyProvider.java | 0 .../property/PointerEventsPropertyProvider.java | 0 .../property/PositionPropertyProvider.java | 0 .../property/PositioningPropertyProvider.java | 0 .../property/TabSizePropertyProvider.java | 0 .../stylesheet/property/TextPropertyProvider.java | 0 .../property/WhiteSpacePropertyProvider.java | 0 .../stylesheet/property/ZIndexPropertyProvider.java | 0 .../stylesheet/selector/CombinatorSelector.java | 0 .../stylesheet/selector/PseudoClassSelector.java | 0 .../stylesheet/selector/PseudoElementSelector.java | 0 .../core/style/stylesheet/selector/Selector.java | 0 .../combinator/AdjacentSiblingSelector.java | 0 .../stylesheet/selector/combinator/AndSelector.java | 0 .../selector/combinator/ChildSelector.java | 0 .../selector/combinator/DescendantSelector.java | 0 .../selector/combinator/GeneralSiblingSelector.java | 0 .../selector/pseudoclass/HoverSelector.java | 0 .../selector/pseudoelement/AfterSelector.java | 0 .../selector/pseudoelement/BeforeSelector.java | 0 .../selector/pseudoelement/ScrollbarSelector.java | 0 .../stylesheet/selector/simple/AllSelector.java | 0 .../selector/simple/ClassAttributeSelector.java | 0 .../stylesheet/selector/simple/ElementSelector.java | 0 .../selector/simple/IdAttributeSelector.java | 0 .../core/style/stylesheet/term/TermColor.java | 0 .../core/style/stylesheet/term/TermFloat.java | 0 .../core/style/stylesheet/term/TermFunction.java | 0 .../core/style/stylesheet/term/TermIdent.java | 0 .../core/style/stylesheet/term/TermInteger.java | 0 .../core/style/stylesheet/term/TermLength.java | 0 .../core/style/stylesheet/term/TermList.java | 0 .../core/style/stylesheet/term/TermURI.java | 0 .../core/style/stylesheet/term/TermUnit.java | 0 .../core/style/stylesheet/util/StyleUtils.java | 0 .../spinygui/core/style/types/BorderRadius.java | 0 .../spinygui/core/style/types/BoxShadow.java | 0 .../spinyowl/spinygui/core/style/types/Color.java | 0 .../spinyowl/spinygui/core/style/types/Display.java | 0 .../spinygui/core/style/types/HorizontalAlign.java | 0 .../spinygui/core/style/types/Overflow.java | 0 .../spinygui/core/style/types/OverflowWrap.java | 0 .../spinygui/core/style/types/PointerEvents.java | 0 .../spinygui/core/style/types/Position.java | 0 .../spinygui/core/style/types/TextAlign.java | 0 .../spinygui/core/style/types/VerticalAlign.java | 0 .../spinygui/core/style/types/WhiteSpace.java | 0 .../spinygui/core/style/types/WordBreak.java | 0 .../style/types/background/BackgroundOrigin.java | 0 .../style/types/background/BackgroundRepeat.java | 0 .../core/style/types/background/BackgroundSize.java | 0 .../core/style/types/border/BorderItem.java | 0 .../core/style/types/border/BorderStyle.java | 0 .../core/style/types/flex/AlignContent.java | 0 .../spinygui/core/style/types/flex/AlignItems.java | 0 .../spinygui/core/style/types/flex/AlignSelf.java | 0 .../core/style/types/flex/FlexDirection.java | 0 .../spinygui/core/style/types/flex/FlexWrap.java | 0 .../core/style/types/flex/JustifyContent.java | 0 .../spinygui/core/style/types/length/Length.java | 0 .../core/style/types/length/LengthConverter.java | 0 .../spinygui/core/style/types/length/Unit.java | 0 .../spinygui/core/system/event/SystemCharEvent.java | 0 .../core/system/event/SystemCharModsEvent.java | 0 .../core/system/event/SystemCursorEnterEvent.java | 0 .../core/system/event/SystemCursorPosEvent.java | 0 .../spinygui/core/system/event/SystemEvent.java | 0 .../core/system/event/SystemFileDropEvent.java | 0 .../system/event/SystemFramebufferSizeEvent.java | 0 .../spinygui/core/system/event/SystemKeyEvent.java | 0 .../core/system/event/SystemMouseClickEvent.java | 0 .../core/system/event/SystemScrollEvent.java | 0 .../core/system/event/SystemWindowCloseEvent.java | 0 .../core/system/event/SystemWindowFocusEvent.java | 0 .../core/system/event/SystemWindowIconifyEvent.java | 0 .../core/system/event/SystemWindowPosEvent.java | 0 .../core/system/event/SystemWindowRefreshEvent.java | 0 .../core/system/event/SystemWindowSizeEvent.java | 0 .../event/listener/AbstractSystemEventListener.java | 0 .../event/listener/SystemCharEventListener.java | 0 .../listener/SystemCursorEnterEventListener.java | 0 .../listener/SystemCursorPosEventListener.java | 0 .../system/event/listener/SystemEventListener.java | 0 .../event/listener/SystemFileDropEventListener.java | 0 .../event/listener/SystemKeyEventListener.java | 0 .../listener/SystemMouseClickEventListener.java | 0 .../event/listener/SystemScrollEventListener.java | 0 .../listener/SystemWindowFocusEventListener.java | 0 .../listener/SystemWindowIconifyEventListener.java | 0 .../listener/SystemWindowPosEventListener.java | 0 .../listener/SystemWindowRefreshEventListener.java | 0 .../listener/SystemWindowSizeEventListener.java | 0 .../listener/SystemWindowsCloseEventListener.java | 0 .../event/processor/SystemEventProcessor.java | 0 .../event/processor/SystemEventProcessorImpl.java | 0 .../event/provider/SystemEventListenerProvider.java | 0 .../provider/SystemEventListenerProviderImpl.java | 0 .../core/system/font/FontDirectoriesProvider.java | 0 .../core/system/font/FontLoadingException.java | 0 .../spinygui/core/system/font/FontMetrics.java | 0 .../spinygui/core/system/font/FontService.java | 0 .../spinygui/core/system/font/FontStorage.java | 0 .../spinygui/core/system/font/SystemFontLoader.java | 0 .../spinygui/core/system/font/TextLineMetrics.java | 0 .../spinygui/core/system/font/TextMetrics.java | 0 .../core/system/font/impl/FontServiceImpl.java | 0 .../core/system/font/impl/FontStorageImpl.java | 0 .../PlatformSpecificFontDirectoriesProvider.java | 0 .../spinygui/core/system/input/SystemKeyAction.java | 0 .../spinygui/core/system/input/SystemKeyMod.java | 0 .../core/system/input/SystemMouseButton.java | 0 .../spinyowl/spinygui/core/time/TimeService.java | 0 .../spinyowl/spinygui/core/util/ClassKeyMap.java | 0 .../com/spinyowl/spinygui/core/util/IOUtil.java | 0 .../spinyowl/spinygui/core/util/NodeUtilities.java | 0 .../com/spinyowl/spinygui/core/util/Reference.java | 0 .../com/spinyowl/spinygui/core/util/TextUtil.java | 0 .../src/main/java/module-info.java | 0 .../src/main/resources/fonts/FontAwesome.otf | Bin .../main/resources/fonts/MaterialIcons-Regular.ttf | Bin .../src/main/resources/fonts/NotoEmoji-Regular.ttf | Bin .../src/main/resources/fonts/Roboto-Bold.ttf | Bin .../src/main/resources/fonts/Roboto-Light.ttf | Bin .../src/main/resources/fonts/Roboto-Regular.ttf | Bin .../src/main/resources/fonts/entypo.ttf | Bin .../main/resources/fonts/materialdesignicons.ttf | Bin .../src/main/resources/logback.xml | 0 .../spinygui/core/layout/impl/BlockLayoutTest.java | 0 .../layout/impl/InlineFormattingContextTest.java | 0 .../property/TextPropertyProviderTest.java | 0 .../event/listener/SystemCharEventListenerTest.java | 0 .../listener/SystemCursorPosEventListenerTest.java | 0 .../listener/SystemFileDropEventListenerTest.java | 0 .../event/listener/SystemKeyEventListenerTest.java | 0 .../listener/SystemMouseClickEventListenerTest.java | 0 .../listener/SystemScrollEventListenerTest.java | 0 .../SystemWindowFocusEventListenerTest.java | 0 .../SystemWindowIconifyEventListenerTest.java | 0 .../listener/SystemWindowPosEventListenerTest.java | 0 .../SystemWindowRefreshEventListenerTest.java | 0 .../listener/SystemWindowSizeEventListenerTest.java | 0 .../SystemWindowsCloseEventListenerTest.java | 0 .../core/system/font/impl/FontServiceImplTest.java | 0 .../spinygui/core/util/ClassKeyMapTest.java | 0 .../com/spinyowl/spinygui/core/util/IOUtilTest.java | 0 {demo.complex => spinygui.demo.complex}/.gitignore | 0 .../build.gradle.kts | 6 +++--- .../com/spinyowl/spinygui/demo/complex/Demo.java | 0 .../spinyowl/spinygui/demo/complex/NvgExample.java | 0 .../src/main/java/module-info.java | 0 .../resources/com/spinyowl/spinygui/demo/index.html | 0 .../resources/com/spinyowl/spinygui/demo/small.html | 0 {demo.simple => spinygui.demo.simple}/.gitignore | 0 .../build.gradle.kts | 0 .../com/spinyowl/spinygui/demo/simple/Main.java | 0 .../spinyowl/spinygui/demo/simple/OtherMain.java | 0 .../src/main/java/module-info.java | 0 .../resources/com/spinyowl/spinygui/demo/index.html | 0 spinygui/build.gradle.kts | 6 +++--- 315 files changed, 18 insertions(+), 14 deletions(-) rename {core.backend.lwjgl.nanovg => spinygui.core.backend.lwjgl.nanovg}/build.gradle.kts (93%) rename {core.backend.lwjgl.nanovg => spinygui.core.backend.lwjgl.nanovg}/src/main/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/NvgBorderRenderer.java (100%) rename {core.backend.lwjgl.nanovg => spinygui.core.backend.lwjgl.nanovg}/src/main/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/NvgElementRenderer.java (100%) rename {core.backend.lwjgl.nanovg => spinygui.core.backend.lwjgl.nanovg}/src/main/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/NvgRenderer.java (100%) rename {core.backend.lwjgl.nanovg => spinygui.core.backend.lwjgl.nanovg}/src/main/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/NvgTextRenderer.java (100%) rename {core.backend.lwjgl.nanovg => spinygui.core.backend.lwjgl.nanovg}/src/main/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/util/NvgColorUtil.java (100%) rename {core.backend.lwjgl.nanovg => spinygui.core.backend.lwjgl.nanovg}/src/main/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/util/NvgRenderUtils.java (100%) rename {core.backend.lwjgl.nanovg => spinygui.core.backend.lwjgl.nanovg}/src/main/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/util/NvgShapes.java (100%) rename {core.backend.lwjgl.nanovg => spinygui.core.backend.lwjgl.nanovg}/src/main/java/module-info.java (100%) rename {core.backend => spinygui.core.backend}/build.gradle.kts (89%) rename {core.backend => spinygui.core.backend}/src/main/java/com/spinyowl/spinygui/core/backend/renderer/Renderer.java (100%) rename {core.backend => spinygui.core.backend}/src/main/java/module-info.java (100%) rename {core => spinygui.core}/.gitignore (100%) rename {core => spinygui.core}/build.gradle.kts (100%) rename {core => spinygui.core}/src/main/antlr/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3.g4 (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/Configuration.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/animation/Animation.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/animation/Animator.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/animation/AnimatorImpl.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/clipboard/Clipboard.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/cursor/Cursor.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/cursor/CursorMod.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/cursor/CursorService.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/event/.gitkeep (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/event/ChangePositionEvent.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/event/ChangeSizeEvent.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/event/ChangeTextEvent.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/event/CharEvent.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/event/CursorEnterEvent.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/event/CursorExitEvent.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/event/Event.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/event/EventTarget.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/event/FileDropEvent.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/event/FocusInEvent.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/event/FocusOutEvent.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/event/KeyboardEvent.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/event/MouseClickEvent.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/event/MouseDragEvent.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/event/ScrollEvent.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/event/WindowCloseEvent.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/event/WindowFocusEvent.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/event/WindowIconifyEvent.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/event/WindowPosEvent.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/event/WindowRefreshEvent.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/event/WindowSizeEvent.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/event/listener/EventListener.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/event/processor/DefaultEventProcessor.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/event/processor/EventProcessor.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/font/Font.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/font/FontSize.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/font/FontStretch.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/font/FontStyle.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/font/FontWeight.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/image/Image.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/input/KeyAction.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/input/KeyCode.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/input/KeyMod.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/input/Keyboard.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/input/KeyboardKey.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/input/KeyboardLayout.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/input/MouseButton.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/input/MouseService.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/input/Shortcut.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/input/ShortcutRegistry.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/input/impl/KeyboardLayoutImpl.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/input/impl/MouseServiceImpl.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/input/impl/ShortcutRegistryImpl.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/layout/.gitkeep (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/layout/ElementLayout.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/layout/InlineFragment.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/layout/Layout.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/layout/LayoutContext.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/layout/LayoutService.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/layout/LineBox.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/layout/TextLayout.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/layout/TextMeasurer.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/layout/impl/BlockLayout.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/layout/impl/FlexLayout.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/layout/impl/FontServiceTextMeasurer.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/layout/impl/InlineFormattingContext.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/layout/impl/LayoutServiceImpl.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/layout/impl/LayoutServiceProvider.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/layout/impl/LayoutUtils.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/layout/impl/NoneLayout.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/layout/impl/TextLayoutImpl.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/node/Element.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/node/EmptyElement.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/node/Frame.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/node/Node.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/node/NodeBuilder.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/node/Text.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/node/intersection/Intersection.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/node/intersection/Intersections.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/node/intersection/RectangleIntersection.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/node/layout/Box.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/node/layout/Edges.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/node/layout/Rect.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/parser/NodeParser.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/parser/StyleSheetParser.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/parser/impl/DefaultNodeParser.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/parser/impl/DefaultStyleSheetParser.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/parser/impl/ParseException.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/parser/impl/StyleSheetParserFactory.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3.interp (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3.tokens (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3BaseListener.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3BaseVisitor.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3Lexer.interp (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3Lexer.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3Lexer.tokens (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3Listener.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3Parser.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3Visitor.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/visitor/AtRuleVisitor.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/visitor/DeclarationListVisitor.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/visitor/DeclarationVisitor.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/visitor/PropertyValueVisitor.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/visitor/RulesetVisitor.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/visitor/SelectorGroupVisitor.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/visitor/SelectorVisitor.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/visitor/StyleSheetVisitor.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/ResolvedStyle.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/manager/StyleManager.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/manager/StyleManagerImpl.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/AtRule.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/Declaration.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/Properties.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/PropertiesScanner.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/Property.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/PropertyProvider.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/PropertyStore.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/PropertyStoreProvider.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/Ruleset.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/Specificity.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/StyleSheet.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/Term.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/annotation/Priority.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/atrule/FontFaceRule.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/impl/DefaultPropertyStore.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/impl/DefaultPropertyStoreProvider.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/BackgroundPropertyProvider.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/BorderColorPropertyProvider.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/BorderPropertyProvider.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/BorderRadiusPropertyProvider.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/BorderStylePropertyProvider.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/BorderWidthPropertyProvider.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/BoxShadowPropertyProvider.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/ColorPropertyProvider.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/DimensionPropertyProvider.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/DisplayPropertyProvider.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/FlexPropertyProvider.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/FontPropertyProvider.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/MarginPropertyProvider.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/OpacityPropertyProvider.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/PaddingPropertyProvider.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/PointerEventsPropertyProvider.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/PositionPropertyProvider.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/PositioningPropertyProvider.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/TabSizePropertyProvider.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/TextPropertyProvider.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/WhiteSpacePropertyProvider.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/ZIndexPropertyProvider.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/CombinatorSelector.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/PseudoClassSelector.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/PseudoElementSelector.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/Selector.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/combinator/AdjacentSiblingSelector.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/combinator/AndSelector.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/combinator/ChildSelector.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/combinator/DescendantSelector.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/combinator/GeneralSiblingSelector.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/pseudoclass/HoverSelector.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/pseudoelement/AfterSelector.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/pseudoelement/BeforeSelector.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/pseudoelement/ScrollbarSelector.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/simple/AllSelector.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/simple/ClassAttributeSelector.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/simple/ElementSelector.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/simple/IdAttributeSelector.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/term/TermColor.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/term/TermFloat.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/term/TermFunction.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/term/TermIdent.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/term/TermInteger.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/term/TermLength.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/term/TermList.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/term/TermURI.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/term/TermUnit.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/util/StyleUtils.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/types/BorderRadius.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/types/BoxShadow.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/types/Color.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/types/Display.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/types/HorizontalAlign.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/types/Overflow.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/types/OverflowWrap.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/types/PointerEvents.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/types/Position.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/types/TextAlign.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/types/VerticalAlign.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/types/WhiteSpace.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/types/WordBreak.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/types/background/BackgroundOrigin.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/types/background/BackgroundRepeat.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/types/background/BackgroundSize.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/types/border/BorderItem.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/types/border/BorderStyle.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/types/flex/AlignContent.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/types/flex/AlignItems.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/types/flex/AlignSelf.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/types/flex/FlexDirection.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/types/flex/FlexWrap.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/types/flex/JustifyContent.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/types/length/Length.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/types/length/LengthConverter.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/style/types/length/Unit.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/system/event/SystemCharEvent.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/system/event/SystemCharModsEvent.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/system/event/SystemCursorEnterEvent.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/system/event/SystemCursorPosEvent.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/system/event/SystemEvent.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/system/event/SystemFileDropEvent.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/system/event/SystemFramebufferSizeEvent.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/system/event/SystemKeyEvent.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/system/event/SystemMouseClickEvent.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/system/event/SystemScrollEvent.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/system/event/SystemWindowCloseEvent.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/system/event/SystemWindowFocusEvent.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/system/event/SystemWindowIconifyEvent.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/system/event/SystemWindowPosEvent.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/system/event/SystemWindowRefreshEvent.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/system/event/SystemWindowSizeEvent.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/system/event/listener/AbstractSystemEventListener.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemCharEventListener.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemCursorEnterEventListener.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemCursorPosEventListener.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemEventListener.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemFileDropEventListener.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemKeyEventListener.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemMouseClickEventListener.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemScrollEventListener.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemWindowFocusEventListener.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemWindowIconifyEventListener.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemWindowPosEventListener.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemWindowRefreshEventListener.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemWindowSizeEventListener.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemWindowsCloseEventListener.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/system/event/processor/SystemEventProcessor.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/system/event/processor/SystemEventProcessorImpl.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/system/event/provider/SystemEventListenerProvider.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/system/event/provider/SystemEventListenerProviderImpl.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/system/font/FontDirectoriesProvider.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/system/font/FontLoadingException.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/system/font/FontMetrics.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/system/font/FontService.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/system/font/FontStorage.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/system/font/SystemFontLoader.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/system/font/TextLineMetrics.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/system/font/TextMetrics.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/system/font/impl/FontServiceImpl.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/system/font/impl/FontStorageImpl.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/system/font/impl/PlatformSpecificFontDirectoriesProvider.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/system/input/SystemKeyAction.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/system/input/SystemKeyMod.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/system/input/SystemMouseButton.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/time/TimeService.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/util/ClassKeyMap.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/util/IOUtil.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/util/NodeUtilities.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/util/Reference.java (100%) rename {core => spinygui.core}/src/main/java/com/spinyowl/spinygui/core/util/TextUtil.java (100%) rename {core => spinygui.core}/src/main/java/module-info.java (100%) rename {core => spinygui.core}/src/main/resources/fonts/FontAwesome.otf (100%) rename {core => spinygui.core}/src/main/resources/fonts/MaterialIcons-Regular.ttf (100%) rename {core => spinygui.core}/src/main/resources/fonts/NotoEmoji-Regular.ttf (100%) rename {core => spinygui.core}/src/main/resources/fonts/Roboto-Bold.ttf (100%) rename {core => spinygui.core}/src/main/resources/fonts/Roboto-Light.ttf (100%) rename {core => spinygui.core}/src/main/resources/fonts/Roboto-Regular.ttf (100%) rename {core => spinygui.core}/src/main/resources/fonts/entypo.ttf (100%) rename {core => spinygui.core}/src/main/resources/fonts/materialdesignicons.ttf (100%) rename {core => spinygui.core}/src/main/resources/logback.xml (100%) rename {core => spinygui.core}/src/test/java/com/spinyowl/spinygui/core/layout/impl/BlockLayoutTest.java (100%) rename {core => spinygui.core}/src/test/java/com/spinyowl/spinygui/core/layout/impl/InlineFormattingContextTest.java (100%) rename {core => spinygui.core}/src/test/java/com/spinyowl/spinygui/core/style/stylesheet/property/TextPropertyProviderTest.java (100%) rename {core => spinygui.core}/src/test/java/com/spinyowl/spinygui/core/system/event/listener/SystemCharEventListenerTest.java (100%) rename {core => spinygui.core}/src/test/java/com/spinyowl/spinygui/core/system/event/listener/SystemCursorPosEventListenerTest.java (100%) rename {core => spinygui.core}/src/test/java/com/spinyowl/spinygui/core/system/event/listener/SystemFileDropEventListenerTest.java (100%) rename {core => spinygui.core}/src/test/java/com/spinyowl/spinygui/core/system/event/listener/SystemKeyEventListenerTest.java (100%) rename {core => spinygui.core}/src/test/java/com/spinyowl/spinygui/core/system/event/listener/SystemMouseClickEventListenerTest.java (100%) rename {core => spinygui.core}/src/test/java/com/spinyowl/spinygui/core/system/event/listener/SystemScrollEventListenerTest.java (100%) rename {core => spinygui.core}/src/test/java/com/spinyowl/spinygui/core/system/event/listener/SystemWindowFocusEventListenerTest.java (100%) rename {core => spinygui.core}/src/test/java/com/spinyowl/spinygui/core/system/event/listener/SystemWindowIconifyEventListenerTest.java (100%) rename {core => spinygui.core}/src/test/java/com/spinyowl/spinygui/core/system/event/listener/SystemWindowPosEventListenerTest.java (100%) rename {core => spinygui.core}/src/test/java/com/spinyowl/spinygui/core/system/event/listener/SystemWindowRefreshEventListenerTest.java (100%) rename {core => spinygui.core}/src/test/java/com/spinyowl/spinygui/core/system/event/listener/SystemWindowSizeEventListenerTest.java (100%) rename {core => spinygui.core}/src/test/java/com/spinyowl/spinygui/core/system/event/listener/SystemWindowsCloseEventListenerTest.java (100%) rename {core => spinygui.core}/src/test/java/com/spinyowl/spinygui/core/system/font/impl/FontServiceImplTest.java (100%) rename {core => spinygui.core}/src/test/java/com/spinyowl/spinygui/core/util/ClassKeyMapTest.java (100%) rename {core => spinygui.core}/src/test/java/com/spinyowl/spinygui/core/util/IOUtilTest.java (100%) rename {demo.complex => spinygui.demo.complex}/.gitignore (100%) rename {demo.complex => spinygui.demo.complex}/build.gradle.kts (91%) rename {demo.complex => spinygui.demo.complex}/src/main/java/com/spinyowl/spinygui/demo/complex/Demo.java (100%) rename {demo.complex => spinygui.demo.complex}/src/main/java/com/spinyowl/spinygui/demo/complex/NvgExample.java (100%) rename {demo.complex => spinygui.demo.complex}/src/main/java/module-info.java (100%) rename {demo.complex => spinygui.demo.complex}/src/main/resources/com/spinyowl/spinygui/demo/index.html (100%) rename {demo.complex => spinygui.demo.complex}/src/main/resources/com/spinyowl/spinygui/demo/small.html (100%) rename {demo.simple => spinygui.demo.simple}/.gitignore (100%) rename {demo.simple => spinygui.demo.simple}/build.gradle.kts (100%) rename {demo.simple => spinygui.demo.simple}/src/main/java/com/spinyowl/spinygui/demo/simple/Main.java (100%) rename {demo.simple => spinygui.demo.simple}/src/main/java/com/spinyowl/spinygui/demo/simple/OtherMain.java (100%) rename {demo.simple => spinygui.demo.simple}/src/main/java/module-info.java (100%) rename {demo.simple => spinygui.demo.simple}/src/main/resources/com/spinyowl/spinygui/demo/index.html (100%) diff --git a/build.gradle.kts b/build.gradle.kts index b7157bcb..c04fae01 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,6 +4,10 @@ plugins { group = "com.spinyowl" +subprojects { + group = rootProject.group +} + java { toolchain { languageVersion = JavaLanguageVersion.of(25) diff --git a/settings.gradle.kts b/settings.gradle.kts index 8011c5f1..fe2e62c7 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -13,9 +13,9 @@ plugins { rootProject.name = "SpinyGUI" include( "spinygui", - "core", - "core.backend", - "core.backend.lwjgl.nanovg", - "demo.simple", - "demo.complex" + "spinygui.core", + "spinygui.core.backend", + "spinygui.core.backend.lwjgl.nanovg", + "spinygui.demo.simple", + "spinygui.demo.complex" ) diff --git a/core.backend.lwjgl.nanovg/build.gradle.kts b/spinygui.core.backend.lwjgl.nanovg/build.gradle.kts similarity index 93% rename from core.backend.lwjgl.nanovg/build.gradle.kts rename to spinygui.core.backend.lwjgl.nanovg/build.gradle.kts index a0d1bdde..62d0d9da 100644 --- a/core.backend.lwjgl.nanovg/build.gradle.kts +++ b/spinygui.core.backend.lwjgl.nanovg/build.gradle.kts @@ -4,8 +4,8 @@ plugins { } dependencies { - api(project(":core")) - api(project(":core.backend")) + api(project(":spinygui.core")) + api(project(":spinygui.core.backend")) api(libs.lwjgl) api(variantOf(libs.lwjgl) { classifier("natives-windows") }) diff --git a/core.backend.lwjgl.nanovg/src/main/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/NvgBorderRenderer.java b/spinygui.core.backend.lwjgl.nanovg/src/main/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/NvgBorderRenderer.java similarity index 100% rename from core.backend.lwjgl.nanovg/src/main/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/NvgBorderRenderer.java rename to spinygui.core.backend.lwjgl.nanovg/src/main/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/NvgBorderRenderer.java diff --git a/core.backend.lwjgl.nanovg/src/main/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/NvgElementRenderer.java b/spinygui.core.backend.lwjgl.nanovg/src/main/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/NvgElementRenderer.java similarity index 100% rename from core.backend.lwjgl.nanovg/src/main/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/NvgElementRenderer.java rename to spinygui.core.backend.lwjgl.nanovg/src/main/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/NvgElementRenderer.java diff --git a/core.backend.lwjgl.nanovg/src/main/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/NvgRenderer.java b/spinygui.core.backend.lwjgl.nanovg/src/main/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/NvgRenderer.java similarity index 100% rename from core.backend.lwjgl.nanovg/src/main/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/NvgRenderer.java rename to spinygui.core.backend.lwjgl.nanovg/src/main/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/NvgRenderer.java diff --git a/core.backend.lwjgl.nanovg/src/main/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/NvgTextRenderer.java b/spinygui.core.backend.lwjgl.nanovg/src/main/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/NvgTextRenderer.java similarity index 100% rename from core.backend.lwjgl.nanovg/src/main/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/NvgTextRenderer.java rename to spinygui.core.backend.lwjgl.nanovg/src/main/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/NvgTextRenderer.java diff --git a/core.backend.lwjgl.nanovg/src/main/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/util/NvgColorUtil.java b/spinygui.core.backend.lwjgl.nanovg/src/main/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/util/NvgColorUtil.java similarity index 100% rename from core.backend.lwjgl.nanovg/src/main/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/util/NvgColorUtil.java rename to spinygui.core.backend.lwjgl.nanovg/src/main/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/util/NvgColorUtil.java diff --git a/core.backend.lwjgl.nanovg/src/main/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/util/NvgRenderUtils.java b/spinygui.core.backend.lwjgl.nanovg/src/main/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/util/NvgRenderUtils.java similarity index 100% rename from core.backend.lwjgl.nanovg/src/main/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/util/NvgRenderUtils.java rename to spinygui.core.backend.lwjgl.nanovg/src/main/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/util/NvgRenderUtils.java diff --git a/core.backend.lwjgl.nanovg/src/main/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/util/NvgShapes.java b/spinygui.core.backend.lwjgl.nanovg/src/main/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/util/NvgShapes.java similarity index 100% rename from core.backend.lwjgl.nanovg/src/main/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/util/NvgShapes.java rename to spinygui.core.backend.lwjgl.nanovg/src/main/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/util/NvgShapes.java diff --git a/core.backend.lwjgl.nanovg/src/main/java/module-info.java b/spinygui.core.backend.lwjgl.nanovg/src/main/java/module-info.java similarity index 100% rename from core.backend.lwjgl.nanovg/src/main/java/module-info.java rename to spinygui.core.backend.lwjgl.nanovg/src/main/java/module-info.java diff --git a/core.backend/build.gradle.kts b/spinygui.core.backend/build.gradle.kts similarity index 89% rename from core.backend/build.gradle.kts rename to spinygui.core.backend/build.gradle.kts index 221a3e1a..eb481a0c 100644 --- a/core.backend/build.gradle.kts +++ b/spinygui.core.backend/build.gradle.kts @@ -4,7 +4,7 @@ plugins { } dependencies { - api(project(":core")) + api(project(":spinygui.core")) } tasks.test { diff --git a/core.backend/src/main/java/com/spinyowl/spinygui/core/backend/renderer/Renderer.java b/spinygui.core.backend/src/main/java/com/spinyowl/spinygui/core/backend/renderer/Renderer.java similarity index 100% rename from core.backend/src/main/java/com/spinyowl/spinygui/core/backend/renderer/Renderer.java rename to spinygui.core.backend/src/main/java/com/spinyowl/spinygui/core/backend/renderer/Renderer.java diff --git a/core.backend/src/main/java/module-info.java b/spinygui.core.backend/src/main/java/module-info.java similarity index 100% rename from core.backend/src/main/java/module-info.java rename to spinygui.core.backend/src/main/java/module-info.java diff --git a/core/.gitignore b/spinygui.core/.gitignore similarity index 100% rename from core/.gitignore rename to spinygui.core/.gitignore diff --git a/core/build.gradle.kts b/spinygui.core/build.gradle.kts similarity index 100% rename from core/build.gradle.kts rename to spinygui.core/build.gradle.kts diff --git a/core/src/main/antlr/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3.g4 b/spinygui.core/src/main/antlr/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3.g4 similarity index 100% rename from core/src/main/antlr/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3.g4 rename to spinygui.core/src/main/antlr/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3.g4 diff --git a/core/src/main/java/com/spinyowl/spinygui/core/Configuration.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/Configuration.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/Configuration.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/Configuration.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/animation/Animation.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/animation/Animation.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/animation/Animation.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/animation/Animation.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/animation/Animator.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/animation/Animator.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/animation/Animator.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/animation/Animator.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/animation/AnimatorImpl.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/animation/AnimatorImpl.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/animation/AnimatorImpl.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/animation/AnimatorImpl.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/clipboard/Clipboard.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/clipboard/Clipboard.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/clipboard/Clipboard.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/clipboard/Clipboard.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/cursor/Cursor.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/cursor/Cursor.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/cursor/Cursor.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/cursor/Cursor.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/cursor/CursorMod.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/cursor/CursorMod.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/cursor/CursorMod.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/cursor/CursorMod.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/cursor/CursorService.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/cursor/CursorService.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/cursor/CursorService.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/cursor/CursorService.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/event/.gitkeep b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/event/.gitkeep similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/event/.gitkeep rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/event/.gitkeep diff --git a/core/src/main/java/com/spinyowl/spinygui/core/event/ChangePositionEvent.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/event/ChangePositionEvent.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/event/ChangePositionEvent.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/event/ChangePositionEvent.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/event/ChangeSizeEvent.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/event/ChangeSizeEvent.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/event/ChangeSizeEvent.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/event/ChangeSizeEvent.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/event/ChangeTextEvent.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/event/ChangeTextEvent.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/event/ChangeTextEvent.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/event/ChangeTextEvent.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/event/CharEvent.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/event/CharEvent.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/event/CharEvent.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/event/CharEvent.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/event/CursorEnterEvent.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/event/CursorEnterEvent.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/event/CursorEnterEvent.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/event/CursorEnterEvent.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/event/CursorExitEvent.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/event/CursorExitEvent.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/event/CursorExitEvent.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/event/CursorExitEvent.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/event/Event.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/event/Event.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/event/Event.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/event/Event.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/event/EventTarget.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/event/EventTarget.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/event/EventTarget.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/event/EventTarget.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/event/FileDropEvent.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/event/FileDropEvent.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/event/FileDropEvent.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/event/FileDropEvent.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/event/FocusInEvent.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/event/FocusInEvent.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/event/FocusInEvent.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/event/FocusInEvent.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/event/FocusOutEvent.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/event/FocusOutEvent.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/event/FocusOutEvent.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/event/FocusOutEvent.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/event/KeyboardEvent.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/event/KeyboardEvent.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/event/KeyboardEvent.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/event/KeyboardEvent.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/event/MouseClickEvent.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/event/MouseClickEvent.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/event/MouseClickEvent.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/event/MouseClickEvent.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/event/MouseDragEvent.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/event/MouseDragEvent.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/event/MouseDragEvent.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/event/MouseDragEvent.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/event/ScrollEvent.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/event/ScrollEvent.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/event/ScrollEvent.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/event/ScrollEvent.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/event/WindowCloseEvent.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/event/WindowCloseEvent.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/event/WindowCloseEvent.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/event/WindowCloseEvent.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/event/WindowFocusEvent.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/event/WindowFocusEvent.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/event/WindowFocusEvent.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/event/WindowFocusEvent.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/event/WindowIconifyEvent.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/event/WindowIconifyEvent.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/event/WindowIconifyEvent.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/event/WindowIconifyEvent.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/event/WindowPosEvent.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/event/WindowPosEvent.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/event/WindowPosEvent.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/event/WindowPosEvent.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/event/WindowRefreshEvent.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/event/WindowRefreshEvent.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/event/WindowRefreshEvent.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/event/WindowRefreshEvent.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/event/WindowSizeEvent.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/event/WindowSizeEvent.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/event/WindowSizeEvent.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/event/WindowSizeEvent.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/event/listener/EventListener.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/event/listener/EventListener.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/event/listener/EventListener.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/event/listener/EventListener.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/event/processor/DefaultEventProcessor.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/event/processor/DefaultEventProcessor.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/event/processor/DefaultEventProcessor.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/event/processor/DefaultEventProcessor.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/event/processor/EventProcessor.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/event/processor/EventProcessor.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/event/processor/EventProcessor.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/event/processor/EventProcessor.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/font/Font.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/font/Font.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/font/Font.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/font/Font.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/font/FontSize.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/font/FontSize.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/font/FontSize.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/font/FontSize.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/font/FontStretch.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/font/FontStretch.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/font/FontStretch.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/font/FontStretch.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/font/FontStyle.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/font/FontStyle.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/font/FontStyle.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/font/FontStyle.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/font/FontWeight.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/font/FontWeight.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/font/FontWeight.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/font/FontWeight.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/image/Image.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/image/Image.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/image/Image.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/image/Image.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/input/KeyAction.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/input/KeyAction.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/input/KeyAction.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/input/KeyAction.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/input/KeyCode.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/input/KeyCode.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/input/KeyCode.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/input/KeyCode.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/input/KeyMod.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/input/KeyMod.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/input/KeyMod.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/input/KeyMod.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/input/Keyboard.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/input/Keyboard.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/input/Keyboard.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/input/Keyboard.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/input/KeyboardKey.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/input/KeyboardKey.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/input/KeyboardKey.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/input/KeyboardKey.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/input/KeyboardLayout.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/input/KeyboardLayout.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/input/KeyboardLayout.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/input/KeyboardLayout.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/input/MouseButton.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/input/MouseButton.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/input/MouseButton.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/input/MouseButton.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/input/MouseService.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/input/MouseService.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/input/MouseService.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/input/MouseService.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/input/Shortcut.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/input/Shortcut.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/input/Shortcut.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/input/Shortcut.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/input/ShortcutRegistry.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/input/ShortcutRegistry.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/input/ShortcutRegistry.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/input/ShortcutRegistry.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/input/impl/KeyboardLayoutImpl.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/input/impl/KeyboardLayoutImpl.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/input/impl/KeyboardLayoutImpl.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/input/impl/KeyboardLayoutImpl.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/input/impl/MouseServiceImpl.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/input/impl/MouseServiceImpl.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/input/impl/MouseServiceImpl.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/input/impl/MouseServiceImpl.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/input/impl/ShortcutRegistryImpl.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/input/impl/ShortcutRegistryImpl.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/input/impl/ShortcutRegistryImpl.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/input/impl/ShortcutRegistryImpl.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/layout/.gitkeep b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/layout/.gitkeep similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/layout/.gitkeep rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/layout/.gitkeep diff --git a/core/src/main/java/com/spinyowl/spinygui/core/layout/ElementLayout.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/layout/ElementLayout.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/layout/ElementLayout.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/layout/ElementLayout.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/layout/InlineFragment.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/layout/InlineFragment.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/layout/InlineFragment.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/layout/InlineFragment.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/layout/Layout.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/layout/Layout.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/layout/Layout.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/layout/Layout.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/layout/LayoutContext.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/layout/LayoutContext.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/layout/LayoutContext.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/layout/LayoutContext.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/layout/LayoutService.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/layout/LayoutService.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/layout/LayoutService.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/layout/LayoutService.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/layout/LineBox.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/layout/LineBox.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/layout/LineBox.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/layout/LineBox.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/layout/TextLayout.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/layout/TextLayout.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/layout/TextLayout.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/layout/TextLayout.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/layout/TextMeasurer.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/layout/TextMeasurer.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/layout/TextMeasurer.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/layout/TextMeasurer.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/layout/impl/BlockLayout.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/layout/impl/BlockLayout.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/layout/impl/BlockLayout.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/layout/impl/BlockLayout.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/layout/impl/FlexLayout.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/layout/impl/FlexLayout.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/layout/impl/FlexLayout.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/layout/impl/FlexLayout.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/layout/impl/FontServiceTextMeasurer.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/layout/impl/FontServiceTextMeasurer.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/layout/impl/FontServiceTextMeasurer.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/layout/impl/FontServiceTextMeasurer.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/layout/impl/InlineFormattingContext.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/layout/impl/InlineFormattingContext.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/layout/impl/InlineFormattingContext.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/layout/impl/InlineFormattingContext.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/layout/impl/LayoutServiceImpl.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/layout/impl/LayoutServiceImpl.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/layout/impl/LayoutServiceImpl.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/layout/impl/LayoutServiceImpl.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/layout/impl/LayoutServiceProvider.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/layout/impl/LayoutServiceProvider.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/layout/impl/LayoutServiceProvider.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/layout/impl/LayoutServiceProvider.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/layout/impl/LayoutUtils.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/layout/impl/LayoutUtils.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/layout/impl/LayoutUtils.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/layout/impl/LayoutUtils.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/layout/impl/NoneLayout.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/layout/impl/NoneLayout.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/layout/impl/NoneLayout.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/layout/impl/NoneLayout.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/layout/impl/TextLayoutImpl.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/layout/impl/TextLayoutImpl.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/layout/impl/TextLayoutImpl.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/layout/impl/TextLayoutImpl.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/node/Element.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/node/Element.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/node/Element.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/node/Element.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/node/EmptyElement.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/node/EmptyElement.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/node/EmptyElement.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/node/EmptyElement.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/node/Frame.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/node/Frame.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/node/Frame.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/node/Frame.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/node/Node.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/node/Node.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/node/Node.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/node/Node.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/node/NodeBuilder.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/node/NodeBuilder.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/node/NodeBuilder.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/node/NodeBuilder.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/node/Text.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/node/Text.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/node/Text.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/node/Text.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/node/intersection/Intersection.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/node/intersection/Intersection.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/node/intersection/Intersection.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/node/intersection/Intersection.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/node/intersection/Intersections.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/node/intersection/Intersections.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/node/intersection/Intersections.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/node/intersection/Intersections.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/node/intersection/RectangleIntersection.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/node/intersection/RectangleIntersection.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/node/intersection/RectangleIntersection.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/node/intersection/RectangleIntersection.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/node/layout/Box.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/node/layout/Box.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/node/layout/Box.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/node/layout/Box.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/node/layout/Edges.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/node/layout/Edges.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/node/layout/Edges.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/node/layout/Edges.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/node/layout/Rect.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/node/layout/Rect.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/node/layout/Rect.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/node/layout/Rect.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/parser/NodeParser.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/parser/NodeParser.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/parser/NodeParser.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/parser/NodeParser.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/parser/StyleSheetParser.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/parser/StyleSheetParser.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/parser/StyleSheetParser.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/parser/StyleSheetParser.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/parser/impl/DefaultNodeParser.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/parser/impl/DefaultNodeParser.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/parser/impl/DefaultNodeParser.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/parser/impl/DefaultNodeParser.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/parser/impl/DefaultStyleSheetParser.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/parser/impl/DefaultStyleSheetParser.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/parser/impl/DefaultStyleSheetParser.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/parser/impl/DefaultStyleSheetParser.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/parser/impl/ParseException.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/parser/impl/ParseException.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/parser/impl/ParseException.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/parser/impl/ParseException.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/parser/impl/StyleSheetParserFactory.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/parser/impl/StyleSheetParserFactory.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/parser/impl/StyleSheetParserFactory.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/parser/impl/StyleSheetParserFactory.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3.interp b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3.interp similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3.interp rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3.interp diff --git a/core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3.tokens b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3.tokens similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3.tokens rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3.tokens diff --git a/core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3BaseListener.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3BaseListener.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3BaseListener.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3BaseListener.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3BaseVisitor.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3BaseVisitor.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3BaseVisitor.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3BaseVisitor.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3Lexer.interp b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3Lexer.interp similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3Lexer.interp rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3Lexer.interp diff --git a/core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3Lexer.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3Lexer.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3Lexer.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3Lexer.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3Lexer.tokens b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3Lexer.tokens similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3Lexer.tokens rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3Lexer.tokens diff --git a/core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3Listener.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3Listener.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3Listener.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3Listener.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3Parser.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3Parser.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3Parser.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3Parser.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3Visitor.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3Visitor.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3Visitor.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/antlr/CSS3Visitor.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/visitor/AtRuleVisitor.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/visitor/AtRuleVisitor.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/visitor/AtRuleVisitor.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/visitor/AtRuleVisitor.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/visitor/DeclarationListVisitor.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/visitor/DeclarationListVisitor.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/visitor/DeclarationListVisitor.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/visitor/DeclarationListVisitor.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/visitor/DeclarationVisitor.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/visitor/DeclarationVisitor.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/visitor/DeclarationVisitor.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/visitor/DeclarationVisitor.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/visitor/PropertyValueVisitor.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/visitor/PropertyValueVisitor.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/visitor/PropertyValueVisitor.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/visitor/PropertyValueVisitor.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/visitor/RulesetVisitor.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/visitor/RulesetVisitor.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/visitor/RulesetVisitor.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/visitor/RulesetVisitor.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/visitor/SelectorGroupVisitor.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/visitor/SelectorGroupVisitor.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/visitor/SelectorGroupVisitor.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/visitor/SelectorGroupVisitor.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/visitor/SelectorVisitor.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/visitor/SelectorVisitor.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/visitor/SelectorVisitor.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/visitor/SelectorVisitor.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/visitor/StyleSheetVisitor.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/visitor/StyleSheetVisitor.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/visitor/StyleSheetVisitor.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/parser/impl/css/visitor/StyleSheetVisitor.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/ResolvedStyle.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/ResolvedStyle.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/ResolvedStyle.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/ResolvedStyle.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/manager/StyleManager.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/manager/StyleManager.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/manager/StyleManager.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/manager/StyleManager.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/manager/StyleManagerImpl.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/manager/StyleManagerImpl.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/manager/StyleManagerImpl.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/manager/StyleManagerImpl.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/AtRule.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/AtRule.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/AtRule.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/AtRule.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/Declaration.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/Declaration.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/Declaration.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/Declaration.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/Properties.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/Properties.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/Properties.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/Properties.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/PropertiesScanner.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/PropertiesScanner.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/PropertiesScanner.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/PropertiesScanner.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/Property.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/Property.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/Property.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/Property.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/PropertyProvider.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/PropertyProvider.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/PropertyProvider.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/PropertyProvider.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/PropertyStore.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/PropertyStore.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/PropertyStore.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/PropertyStore.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/PropertyStoreProvider.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/PropertyStoreProvider.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/PropertyStoreProvider.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/PropertyStoreProvider.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/Ruleset.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/Ruleset.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/Ruleset.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/Ruleset.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/Specificity.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/Specificity.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/Specificity.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/Specificity.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/StyleSheet.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/StyleSheet.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/StyleSheet.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/StyleSheet.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/Term.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/Term.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/Term.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/Term.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/annotation/Priority.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/annotation/Priority.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/annotation/Priority.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/annotation/Priority.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/atrule/FontFaceRule.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/atrule/FontFaceRule.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/atrule/FontFaceRule.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/atrule/FontFaceRule.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/impl/DefaultPropertyStore.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/impl/DefaultPropertyStore.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/impl/DefaultPropertyStore.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/impl/DefaultPropertyStore.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/impl/DefaultPropertyStoreProvider.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/impl/DefaultPropertyStoreProvider.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/impl/DefaultPropertyStoreProvider.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/impl/DefaultPropertyStoreProvider.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/BackgroundPropertyProvider.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/BackgroundPropertyProvider.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/BackgroundPropertyProvider.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/BackgroundPropertyProvider.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/BorderColorPropertyProvider.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/BorderColorPropertyProvider.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/BorderColorPropertyProvider.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/BorderColorPropertyProvider.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/BorderPropertyProvider.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/BorderPropertyProvider.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/BorderPropertyProvider.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/BorderPropertyProvider.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/BorderRadiusPropertyProvider.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/BorderRadiusPropertyProvider.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/BorderRadiusPropertyProvider.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/BorderRadiusPropertyProvider.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/BorderStylePropertyProvider.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/BorderStylePropertyProvider.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/BorderStylePropertyProvider.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/BorderStylePropertyProvider.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/BorderWidthPropertyProvider.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/BorderWidthPropertyProvider.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/BorderWidthPropertyProvider.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/BorderWidthPropertyProvider.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/BoxShadowPropertyProvider.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/BoxShadowPropertyProvider.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/BoxShadowPropertyProvider.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/BoxShadowPropertyProvider.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/ColorPropertyProvider.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/ColorPropertyProvider.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/ColorPropertyProvider.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/ColorPropertyProvider.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/DimensionPropertyProvider.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/DimensionPropertyProvider.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/DimensionPropertyProvider.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/DimensionPropertyProvider.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/DisplayPropertyProvider.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/DisplayPropertyProvider.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/DisplayPropertyProvider.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/DisplayPropertyProvider.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/FlexPropertyProvider.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/FlexPropertyProvider.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/FlexPropertyProvider.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/FlexPropertyProvider.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/FontPropertyProvider.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/FontPropertyProvider.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/FontPropertyProvider.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/FontPropertyProvider.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/MarginPropertyProvider.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/MarginPropertyProvider.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/MarginPropertyProvider.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/MarginPropertyProvider.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/OpacityPropertyProvider.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/OpacityPropertyProvider.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/OpacityPropertyProvider.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/OpacityPropertyProvider.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/PaddingPropertyProvider.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/PaddingPropertyProvider.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/PaddingPropertyProvider.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/PaddingPropertyProvider.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/PointerEventsPropertyProvider.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/PointerEventsPropertyProvider.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/PointerEventsPropertyProvider.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/PointerEventsPropertyProvider.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/PositionPropertyProvider.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/PositionPropertyProvider.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/PositionPropertyProvider.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/PositionPropertyProvider.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/PositioningPropertyProvider.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/PositioningPropertyProvider.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/PositioningPropertyProvider.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/PositioningPropertyProvider.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/TabSizePropertyProvider.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/TabSizePropertyProvider.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/TabSizePropertyProvider.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/TabSizePropertyProvider.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/TextPropertyProvider.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/TextPropertyProvider.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/TextPropertyProvider.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/TextPropertyProvider.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/WhiteSpacePropertyProvider.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/WhiteSpacePropertyProvider.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/WhiteSpacePropertyProvider.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/WhiteSpacePropertyProvider.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/ZIndexPropertyProvider.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/ZIndexPropertyProvider.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/ZIndexPropertyProvider.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/ZIndexPropertyProvider.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/CombinatorSelector.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/CombinatorSelector.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/CombinatorSelector.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/CombinatorSelector.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/PseudoClassSelector.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/PseudoClassSelector.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/PseudoClassSelector.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/PseudoClassSelector.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/PseudoElementSelector.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/PseudoElementSelector.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/PseudoElementSelector.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/PseudoElementSelector.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/Selector.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/Selector.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/Selector.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/Selector.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/combinator/AdjacentSiblingSelector.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/combinator/AdjacentSiblingSelector.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/combinator/AdjacentSiblingSelector.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/combinator/AdjacentSiblingSelector.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/combinator/AndSelector.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/combinator/AndSelector.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/combinator/AndSelector.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/combinator/AndSelector.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/combinator/ChildSelector.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/combinator/ChildSelector.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/combinator/ChildSelector.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/combinator/ChildSelector.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/combinator/DescendantSelector.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/combinator/DescendantSelector.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/combinator/DescendantSelector.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/combinator/DescendantSelector.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/combinator/GeneralSiblingSelector.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/combinator/GeneralSiblingSelector.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/combinator/GeneralSiblingSelector.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/combinator/GeneralSiblingSelector.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/pseudoclass/HoverSelector.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/pseudoclass/HoverSelector.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/pseudoclass/HoverSelector.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/pseudoclass/HoverSelector.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/pseudoelement/AfterSelector.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/pseudoelement/AfterSelector.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/pseudoelement/AfterSelector.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/pseudoelement/AfterSelector.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/pseudoelement/BeforeSelector.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/pseudoelement/BeforeSelector.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/pseudoelement/BeforeSelector.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/pseudoelement/BeforeSelector.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/pseudoelement/ScrollbarSelector.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/pseudoelement/ScrollbarSelector.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/pseudoelement/ScrollbarSelector.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/pseudoelement/ScrollbarSelector.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/simple/AllSelector.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/simple/AllSelector.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/simple/AllSelector.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/simple/AllSelector.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/simple/ClassAttributeSelector.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/simple/ClassAttributeSelector.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/simple/ClassAttributeSelector.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/simple/ClassAttributeSelector.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/simple/ElementSelector.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/simple/ElementSelector.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/simple/ElementSelector.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/simple/ElementSelector.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/simple/IdAttributeSelector.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/simple/IdAttributeSelector.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/simple/IdAttributeSelector.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/selector/simple/IdAttributeSelector.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/term/TermColor.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/term/TermColor.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/term/TermColor.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/term/TermColor.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/term/TermFloat.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/term/TermFloat.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/term/TermFloat.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/term/TermFloat.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/term/TermFunction.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/term/TermFunction.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/term/TermFunction.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/term/TermFunction.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/term/TermIdent.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/term/TermIdent.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/term/TermIdent.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/term/TermIdent.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/term/TermInteger.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/term/TermInteger.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/term/TermInteger.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/term/TermInteger.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/term/TermLength.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/term/TermLength.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/term/TermLength.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/term/TermLength.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/term/TermList.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/term/TermList.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/term/TermList.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/term/TermList.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/term/TermURI.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/term/TermURI.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/term/TermURI.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/term/TermURI.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/term/TermUnit.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/term/TermUnit.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/term/TermUnit.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/term/TermUnit.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/util/StyleUtils.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/util/StyleUtils.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/util/StyleUtils.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/util/StyleUtils.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/types/BorderRadius.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/types/BorderRadius.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/types/BorderRadius.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/types/BorderRadius.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/types/BoxShadow.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/types/BoxShadow.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/types/BoxShadow.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/types/BoxShadow.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/types/Color.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/types/Color.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/types/Color.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/types/Color.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/types/Display.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/types/Display.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/types/Display.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/types/Display.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/types/HorizontalAlign.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/types/HorizontalAlign.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/types/HorizontalAlign.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/types/HorizontalAlign.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/types/Overflow.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/types/Overflow.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/types/Overflow.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/types/Overflow.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/types/OverflowWrap.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/types/OverflowWrap.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/types/OverflowWrap.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/types/OverflowWrap.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/types/PointerEvents.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/types/PointerEvents.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/types/PointerEvents.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/types/PointerEvents.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/types/Position.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/types/Position.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/types/Position.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/types/Position.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/types/TextAlign.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/types/TextAlign.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/types/TextAlign.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/types/TextAlign.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/types/VerticalAlign.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/types/VerticalAlign.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/types/VerticalAlign.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/types/VerticalAlign.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/types/WhiteSpace.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/types/WhiteSpace.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/types/WhiteSpace.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/types/WhiteSpace.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/types/WordBreak.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/types/WordBreak.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/types/WordBreak.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/types/WordBreak.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/types/background/BackgroundOrigin.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/types/background/BackgroundOrigin.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/types/background/BackgroundOrigin.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/types/background/BackgroundOrigin.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/types/background/BackgroundRepeat.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/types/background/BackgroundRepeat.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/types/background/BackgroundRepeat.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/types/background/BackgroundRepeat.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/types/background/BackgroundSize.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/types/background/BackgroundSize.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/types/background/BackgroundSize.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/types/background/BackgroundSize.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/types/border/BorderItem.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/types/border/BorderItem.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/types/border/BorderItem.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/types/border/BorderItem.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/types/border/BorderStyle.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/types/border/BorderStyle.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/types/border/BorderStyle.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/types/border/BorderStyle.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/types/flex/AlignContent.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/types/flex/AlignContent.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/types/flex/AlignContent.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/types/flex/AlignContent.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/types/flex/AlignItems.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/types/flex/AlignItems.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/types/flex/AlignItems.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/types/flex/AlignItems.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/types/flex/AlignSelf.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/types/flex/AlignSelf.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/types/flex/AlignSelf.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/types/flex/AlignSelf.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/types/flex/FlexDirection.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/types/flex/FlexDirection.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/types/flex/FlexDirection.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/types/flex/FlexDirection.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/types/flex/FlexWrap.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/types/flex/FlexWrap.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/types/flex/FlexWrap.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/types/flex/FlexWrap.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/types/flex/JustifyContent.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/types/flex/JustifyContent.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/types/flex/JustifyContent.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/types/flex/JustifyContent.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/types/length/Length.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/types/length/Length.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/types/length/Length.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/types/length/Length.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/types/length/LengthConverter.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/types/length/LengthConverter.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/types/length/LengthConverter.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/types/length/LengthConverter.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/style/types/length/Unit.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/types/length/Unit.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/style/types/length/Unit.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/types/length/Unit.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemCharEvent.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemCharEvent.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemCharEvent.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemCharEvent.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemCharModsEvent.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemCharModsEvent.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemCharModsEvent.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemCharModsEvent.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemCursorEnterEvent.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemCursorEnterEvent.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemCursorEnterEvent.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemCursorEnterEvent.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemCursorPosEvent.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemCursorPosEvent.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemCursorPosEvent.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemCursorPosEvent.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemEvent.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemEvent.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemEvent.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemEvent.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemFileDropEvent.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemFileDropEvent.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemFileDropEvent.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemFileDropEvent.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemFramebufferSizeEvent.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemFramebufferSizeEvent.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemFramebufferSizeEvent.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemFramebufferSizeEvent.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemKeyEvent.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemKeyEvent.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemKeyEvent.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemKeyEvent.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemMouseClickEvent.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemMouseClickEvent.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemMouseClickEvent.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemMouseClickEvent.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemScrollEvent.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemScrollEvent.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemScrollEvent.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemScrollEvent.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemWindowCloseEvent.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemWindowCloseEvent.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemWindowCloseEvent.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemWindowCloseEvent.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemWindowFocusEvent.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemWindowFocusEvent.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemWindowFocusEvent.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemWindowFocusEvent.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemWindowIconifyEvent.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemWindowIconifyEvent.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemWindowIconifyEvent.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemWindowIconifyEvent.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemWindowPosEvent.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemWindowPosEvent.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemWindowPosEvent.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemWindowPosEvent.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemWindowRefreshEvent.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemWindowRefreshEvent.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemWindowRefreshEvent.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemWindowRefreshEvent.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemWindowSizeEvent.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemWindowSizeEvent.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemWindowSizeEvent.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/event/SystemWindowSizeEvent.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/system/event/listener/AbstractSystemEventListener.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/event/listener/AbstractSystemEventListener.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/system/event/listener/AbstractSystemEventListener.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/event/listener/AbstractSystemEventListener.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemCharEventListener.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemCharEventListener.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemCharEventListener.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemCharEventListener.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemCursorEnterEventListener.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemCursorEnterEventListener.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemCursorEnterEventListener.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemCursorEnterEventListener.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemCursorPosEventListener.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemCursorPosEventListener.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemCursorPosEventListener.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemCursorPosEventListener.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemEventListener.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemEventListener.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemEventListener.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemEventListener.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemFileDropEventListener.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemFileDropEventListener.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemFileDropEventListener.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemFileDropEventListener.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemKeyEventListener.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemKeyEventListener.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemKeyEventListener.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemKeyEventListener.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemMouseClickEventListener.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemMouseClickEventListener.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemMouseClickEventListener.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemMouseClickEventListener.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemScrollEventListener.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemScrollEventListener.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemScrollEventListener.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemScrollEventListener.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemWindowFocusEventListener.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemWindowFocusEventListener.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemWindowFocusEventListener.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemWindowFocusEventListener.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemWindowIconifyEventListener.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemWindowIconifyEventListener.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemWindowIconifyEventListener.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemWindowIconifyEventListener.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemWindowPosEventListener.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemWindowPosEventListener.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemWindowPosEventListener.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemWindowPosEventListener.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemWindowRefreshEventListener.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemWindowRefreshEventListener.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemWindowRefreshEventListener.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemWindowRefreshEventListener.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemWindowSizeEventListener.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemWindowSizeEventListener.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemWindowSizeEventListener.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemWindowSizeEventListener.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemWindowsCloseEventListener.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemWindowsCloseEventListener.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemWindowsCloseEventListener.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemWindowsCloseEventListener.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/system/event/processor/SystemEventProcessor.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/event/processor/SystemEventProcessor.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/system/event/processor/SystemEventProcessor.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/event/processor/SystemEventProcessor.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/system/event/processor/SystemEventProcessorImpl.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/event/processor/SystemEventProcessorImpl.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/system/event/processor/SystemEventProcessorImpl.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/event/processor/SystemEventProcessorImpl.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/system/event/provider/SystemEventListenerProvider.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/event/provider/SystemEventListenerProvider.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/system/event/provider/SystemEventListenerProvider.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/event/provider/SystemEventListenerProvider.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/system/event/provider/SystemEventListenerProviderImpl.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/event/provider/SystemEventListenerProviderImpl.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/system/event/provider/SystemEventListenerProviderImpl.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/event/provider/SystemEventListenerProviderImpl.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/system/font/FontDirectoriesProvider.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/font/FontDirectoriesProvider.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/system/font/FontDirectoriesProvider.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/font/FontDirectoriesProvider.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/system/font/FontLoadingException.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/font/FontLoadingException.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/system/font/FontLoadingException.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/font/FontLoadingException.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/system/font/FontMetrics.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/font/FontMetrics.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/system/font/FontMetrics.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/font/FontMetrics.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/system/font/FontService.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/font/FontService.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/system/font/FontService.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/font/FontService.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/system/font/FontStorage.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/font/FontStorage.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/system/font/FontStorage.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/font/FontStorage.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/system/font/SystemFontLoader.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/font/SystemFontLoader.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/system/font/SystemFontLoader.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/font/SystemFontLoader.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/system/font/TextLineMetrics.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/font/TextLineMetrics.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/system/font/TextLineMetrics.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/font/TextLineMetrics.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/system/font/TextMetrics.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/font/TextMetrics.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/system/font/TextMetrics.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/font/TextMetrics.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/system/font/impl/FontServiceImpl.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/font/impl/FontServiceImpl.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/system/font/impl/FontServiceImpl.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/font/impl/FontServiceImpl.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/system/font/impl/FontStorageImpl.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/font/impl/FontStorageImpl.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/system/font/impl/FontStorageImpl.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/font/impl/FontStorageImpl.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/system/font/impl/PlatformSpecificFontDirectoriesProvider.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/font/impl/PlatformSpecificFontDirectoriesProvider.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/system/font/impl/PlatformSpecificFontDirectoriesProvider.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/font/impl/PlatformSpecificFontDirectoriesProvider.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/system/input/SystemKeyAction.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/input/SystemKeyAction.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/system/input/SystemKeyAction.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/input/SystemKeyAction.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/system/input/SystemKeyMod.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/input/SystemKeyMod.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/system/input/SystemKeyMod.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/input/SystemKeyMod.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/system/input/SystemMouseButton.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/input/SystemMouseButton.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/system/input/SystemMouseButton.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/input/SystemMouseButton.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/time/TimeService.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/time/TimeService.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/time/TimeService.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/time/TimeService.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/util/ClassKeyMap.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/util/ClassKeyMap.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/util/ClassKeyMap.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/util/ClassKeyMap.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/util/IOUtil.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/util/IOUtil.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/util/IOUtil.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/util/IOUtil.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/util/NodeUtilities.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/util/NodeUtilities.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/util/NodeUtilities.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/util/NodeUtilities.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/util/Reference.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/util/Reference.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/util/Reference.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/util/Reference.java diff --git a/core/src/main/java/com/spinyowl/spinygui/core/util/TextUtil.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/util/TextUtil.java similarity index 100% rename from core/src/main/java/com/spinyowl/spinygui/core/util/TextUtil.java rename to spinygui.core/src/main/java/com/spinyowl/spinygui/core/util/TextUtil.java diff --git a/core/src/main/java/module-info.java b/spinygui.core/src/main/java/module-info.java similarity index 100% rename from core/src/main/java/module-info.java rename to spinygui.core/src/main/java/module-info.java diff --git a/core/src/main/resources/fonts/FontAwesome.otf b/spinygui.core/src/main/resources/fonts/FontAwesome.otf similarity index 100% rename from core/src/main/resources/fonts/FontAwesome.otf rename to spinygui.core/src/main/resources/fonts/FontAwesome.otf diff --git a/core/src/main/resources/fonts/MaterialIcons-Regular.ttf b/spinygui.core/src/main/resources/fonts/MaterialIcons-Regular.ttf similarity index 100% rename from core/src/main/resources/fonts/MaterialIcons-Regular.ttf rename to spinygui.core/src/main/resources/fonts/MaterialIcons-Regular.ttf diff --git a/core/src/main/resources/fonts/NotoEmoji-Regular.ttf b/spinygui.core/src/main/resources/fonts/NotoEmoji-Regular.ttf similarity index 100% rename from core/src/main/resources/fonts/NotoEmoji-Regular.ttf rename to spinygui.core/src/main/resources/fonts/NotoEmoji-Regular.ttf diff --git a/core/src/main/resources/fonts/Roboto-Bold.ttf b/spinygui.core/src/main/resources/fonts/Roboto-Bold.ttf similarity index 100% rename from core/src/main/resources/fonts/Roboto-Bold.ttf rename to spinygui.core/src/main/resources/fonts/Roboto-Bold.ttf diff --git a/core/src/main/resources/fonts/Roboto-Light.ttf b/spinygui.core/src/main/resources/fonts/Roboto-Light.ttf similarity index 100% rename from core/src/main/resources/fonts/Roboto-Light.ttf rename to spinygui.core/src/main/resources/fonts/Roboto-Light.ttf diff --git a/core/src/main/resources/fonts/Roboto-Regular.ttf b/spinygui.core/src/main/resources/fonts/Roboto-Regular.ttf similarity index 100% rename from core/src/main/resources/fonts/Roboto-Regular.ttf rename to spinygui.core/src/main/resources/fonts/Roboto-Regular.ttf diff --git a/core/src/main/resources/fonts/entypo.ttf b/spinygui.core/src/main/resources/fonts/entypo.ttf similarity index 100% rename from core/src/main/resources/fonts/entypo.ttf rename to spinygui.core/src/main/resources/fonts/entypo.ttf diff --git a/core/src/main/resources/fonts/materialdesignicons.ttf b/spinygui.core/src/main/resources/fonts/materialdesignicons.ttf similarity index 100% rename from core/src/main/resources/fonts/materialdesignicons.ttf rename to spinygui.core/src/main/resources/fonts/materialdesignicons.ttf diff --git a/core/src/main/resources/logback.xml b/spinygui.core/src/main/resources/logback.xml similarity index 100% rename from core/src/main/resources/logback.xml rename to spinygui.core/src/main/resources/logback.xml diff --git a/core/src/test/java/com/spinyowl/spinygui/core/layout/impl/BlockLayoutTest.java b/spinygui.core/src/test/java/com/spinyowl/spinygui/core/layout/impl/BlockLayoutTest.java similarity index 100% rename from core/src/test/java/com/spinyowl/spinygui/core/layout/impl/BlockLayoutTest.java rename to spinygui.core/src/test/java/com/spinyowl/spinygui/core/layout/impl/BlockLayoutTest.java diff --git a/core/src/test/java/com/spinyowl/spinygui/core/layout/impl/InlineFormattingContextTest.java b/spinygui.core/src/test/java/com/spinyowl/spinygui/core/layout/impl/InlineFormattingContextTest.java similarity index 100% rename from core/src/test/java/com/spinyowl/spinygui/core/layout/impl/InlineFormattingContextTest.java rename to spinygui.core/src/test/java/com/spinyowl/spinygui/core/layout/impl/InlineFormattingContextTest.java diff --git a/core/src/test/java/com/spinyowl/spinygui/core/style/stylesheet/property/TextPropertyProviderTest.java b/spinygui.core/src/test/java/com/spinyowl/spinygui/core/style/stylesheet/property/TextPropertyProviderTest.java similarity index 100% rename from core/src/test/java/com/spinyowl/spinygui/core/style/stylesheet/property/TextPropertyProviderTest.java rename to spinygui.core/src/test/java/com/spinyowl/spinygui/core/style/stylesheet/property/TextPropertyProviderTest.java diff --git a/core/src/test/java/com/spinyowl/spinygui/core/system/event/listener/SystemCharEventListenerTest.java b/spinygui.core/src/test/java/com/spinyowl/spinygui/core/system/event/listener/SystemCharEventListenerTest.java similarity index 100% rename from core/src/test/java/com/spinyowl/spinygui/core/system/event/listener/SystemCharEventListenerTest.java rename to spinygui.core/src/test/java/com/spinyowl/spinygui/core/system/event/listener/SystemCharEventListenerTest.java diff --git a/core/src/test/java/com/spinyowl/spinygui/core/system/event/listener/SystemCursorPosEventListenerTest.java b/spinygui.core/src/test/java/com/spinyowl/spinygui/core/system/event/listener/SystemCursorPosEventListenerTest.java similarity index 100% rename from core/src/test/java/com/spinyowl/spinygui/core/system/event/listener/SystemCursorPosEventListenerTest.java rename to spinygui.core/src/test/java/com/spinyowl/spinygui/core/system/event/listener/SystemCursorPosEventListenerTest.java diff --git a/core/src/test/java/com/spinyowl/spinygui/core/system/event/listener/SystemFileDropEventListenerTest.java b/spinygui.core/src/test/java/com/spinyowl/spinygui/core/system/event/listener/SystemFileDropEventListenerTest.java similarity index 100% rename from core/src/test/java/com/spinyowl/spinygui/core/system/event/listener/SystemFileDropEventListenerTest.java rename to spinygui.core/src/test/java/com/spinyowl/spinygui/core/system/event/listener/SystemFileDropEventListenerTest.java diff --git a/core/src/test/java/com/spinyowl/spinygui/core/system/event/listener/SystemKeyEventListenerTest.java b/spinygui.core/src/test/java/com/spinyowl/spinygui/core/system/event/listener/SystemKeyEventListenerTest.java similarity index 100% rename from core/src/test/java/com/spinyowl/spinygui/core/system/event/listener/SystemKeyEventListenerTest.java rename to spinygui.core/src/test/java/com/spinyowl/spinygui/core/system/event/listener/SystemKeyEventListenerTest.java diff --git a/core/src/test/java/com/spinyowl/spinygui/core/system/event/listener/SystemMouseClickEventListenerTest.java b/spinygui.core/src/test/java/com/spinyowl/spinygui/core/system/event/listener/SystemMouseClickEventListenerTest.java similarity index 100% rename from core/src/test/java/com/spinyowl/spinygui/core/system/event/listener/SystemMouseClickEventListenerTest.java rename to spinygui.core/src/test/java/com/spinyowl/spinygui/core/system/event/listener/SystemMouseClickEventListenerTest.java diff --git a/core/src/test/java/com/spinyowl/spinygui/core/system/event/listener/SystemScrollEventListenerTest.java b/spinygui.core/src/test/java/com/spinyowl/spinygui/core/system/event/listener/SystemScrollEventListenerTest.java similarity index 100% rename from core/src/test/java/com/spinyowl/spinygui/core/system/event/listener/SystemScrollEventListenerTest.java rename to spinygui.core/src/test/java/com/spinyowl/spinygui/core/system/event/listener/SystemScrollEventListenerTest.java diff --git a/core/src/test/java/com/spinyowl/spinygui/core/system/event/listener/SystemWindowFocusEventListenerTest.java b/spinygui.core/src/test/java/com/spinyowl/spinygui/core/system/event/listener/SystemWindowFocusEventListenerTest.java similarity index 100% rename from core/src/test/java/com/spinyowl/spinygui/core/system/event/listener/SystemWindowFocusEventListenerTest.java rename to spinygui.core/src/test/java/com/spinyowl/spinygui/core/system/event/listener/SystemWindowFocusEventListenerTest.java diff --git a/core/src/test/java/com/spinyowl/spinygui/core/system/event/listener/SystemWindowIconifyEventListenerTest.java b/spinygui.core/src/test/java/com/spinyowl/spinygui/core/system/event/listener/SystemWindowIconifyEventListenerTest.java similarity index 100% rename from core/src/test/java/com/spinyowl/spinygui/core/system/event/listener/SystemWindowIconifyEventListenerTest.java rename to spinygui.core/src/test/java/com/spinyowl/spinygui/core/system/event/listener/SystemWindowIconifyEventListenerTest.java diff --git a/core/src/test/java/com/spinyowl/spinygui/core/system/event/listener/SystemWindowPosEventListenerTest.java b/spinygui.core/src/test/java/com/spinyowl/spinygui/core/system/event/listener/SystemWindowPosEventListenerTest.java similarity index 100% rename from core/src/test/java/com/spinyowl/spinygui/core/system/event/listener/SystemWindowPosEventListenerTest.java rename to spinygui.core/src/test/java/com/spinyowl/spinygui/core/system/event/listener/SystemWindowPosEventListenerTest.java diff --git a/core/src/test/java/com/spinyowl/spinygui/core/system/event/listener/SystemWindowRefreshEventListenerTest.java b/spinygui.core/src/test/java/com/spinyowl/spinygui/core/system/event/listener/SystemWindowRefreshEventListenerTest.java similarity index 100% rename from core/src/test/java/com/spinyowl/spinygui/core/system/event/listener/SystemWindowRefreshEventListenerTest.java rename to spinygui.core/src/test/java/com/spinyowl/spinygui/core/system/event/listener/SystemWindowRefreshEventListenerTest.java diff --git a/core/src/test/java/com/spinyowl/spinygui/core/system/event/listener/SystemWindowSizeEventListenerTest.java b/spinygui.core/src/test/java/com/spinyowl/spinygui/core/system/event/listener/SystemWindowSizeEventListenerTest.java similarity index 100% rename from core/src/test/java/com/spinyowl/spinygui/core/system/event/listener/SystemWindowSizeEventListenerTest.java rename to spinygui.core/src/test/java/com/spinyowl/spinygui/core/system/event/listener/SystemWindowSizeEventListenerTest.java diff --git a/core/src/test/java/com/spinyowl/spinygui/core/system/event/listener/SystemWindowsCloseEventListenerTest.java b/spinygui.core/src/test/java/com/spinyowl/spinygui/core/system/event/listener/SystemWindowsCloseEventListenerTest.java similarity index 100% rename from core/src/test/java/com/spinyowl/spinygui/core/system/event/listener/SystemWindowsCloseEventListenerTest.java rename to spinygui.core/src/test/java/com/spinyowl/spinygui/core/system/event/listener/SystemWindowsCloseEventListenerTest.java diff --git a/core/src/test/java/com/spinyowl/spinygui/core/system/font/impl/FontServiceImplTest.java b/spinygui.core/src/test/java/com/spinyowl/spinygui/core/system/font/impl/FontServiceImplTest.java similarity index 100% rename from core/src/test/java/com/spinyowl/spinygui/core/system/font/impl/FontServiceImplTest.java rename to spinygui.core/src/test/java/com/spinyowl/spinygui/core/system/font/impl/FontServiceImplTest.java diff --git a/core/src/test/java/com/spinyowl/spinygui/core/util/ClassKeyMapTest.java b/spinygui.core/src/test/java/com/spinyowl/spinygui/core/util/ClassKeyMapTest.java similarity index 100% rename from core/src/test/java/com/spinyowl/spinygui/core/util/ClassKeyMapTest.java rename to spinygui.core/src/test/java/com/spinyowl/spinygui/core/util/ClassKeyMapTest.java diff --git a/core/src/test/java/com/spinyowl/spinygui/core/util/IOUtilTest.java b/spinygui.core/src/test/java/com/spinyowl/spinygui/core/util/IOUtilTest.java similarity index 100% rename from core/src/test/java/com/spinyowl/spinygui/core/util/IOUtilTest.java rename to spinygui.core/src/test/java/com/spinyowl/spinygui/core/util/IOUtilTest.java diff --git a/demo.complex/.gitignore b/spinygui.demo.complex/.gitignore similarity index 100% rename from demo.complex/.gitignore rename to spinygui.demo.complex/.gitignore diff --git a/demo.complex/build.gradle.kts b/spinygui.demo.complex/build.gradle.kts similarity index 91% rename from demo.complex/build.gradle.kts rename to spinygui.demo.complex/build.gradle.kts index 13ddbacc..d2ce989b 100644 --- a/demo.complex/build.gradle.kts +++ b/spinygui.demo.complex/build.gradle.kts @@ -3,9 +3,9 @@ plugins { } dependencies { - api(project(":core")) - api(project(":core.backend")) - api(project(":core.backend.lwjgl.nanovg")) + api(project(":spinygui.core")) + api(project(":spinygui.core.backend")) + api(project(":spinygui.core.backend.lwjgl.nanovg")) api(libs.cbchain) diff --git a/demo.complex/src/main/java/com/spinyowl/spinygui/demo/complex/Demo.java b/spinygui.demo.complex/src/main/java/com/spinyowl/spinygui/demo/complex/Demo.java similarity index 100% rename from demo.complex/src/main/java/com/spinyowl/spinygui/demo/complex/Demo.java rename to spinygui.demo.complex/src/main/java/com/spinyowl/spinygui/demo/complex/Demo.java diff --git a/demo.complex/src/main/java/com/spinyowl/spinygui/demo/complex/NvgExample.java b/spinygui.demo.complex/src/main/java/com/spinyowl/spinygui/demo/complex/NvgExample.java similarity index 100% rename from demo.complex/src/main/java/com/spinyowl/spinygui/demo/complex/NvgExample.java rename to spinygui.demo.complex/src/main/java/com/spinyowl/spinygui/demo/complex/NvgExample.java diff --git a/demo.complex/src/main/java/module-info.java b/spinygui.demo.complex/src/main/java/module-info.java similarity index 100% rename from demo.complex/src/main/java/module-info.java rename to spinygui.demo.complex/src/main/java/module-info.java diff --git a/demo.complex/src/main/resources/com/spinyowl/spinygui/demo/index.html b/spinygui.demo.complex/src/main/resources/com/spinyowl/spinygui/demo/index.html similarity index 100% rename from demo.complex/src/main/resources/com/spinyowl/spinygui/demo/index.html rename to spinygui.demo.complex/src/main/resources/com/spinyowl/spinygui/demo/index.html diff --git a/demo.complex/src/main/resources/com/spinyowl/spinygui/demo/small.html b/spinygui.demo.complex/src/main/resources/com/spinyowl/spinygui/demo/small.html similarity index 100% rename from demo.complex/src/main/resources/com/spinyowl/spinygui/demo/small.html rename to spinygui.demo.complex/src/main/resources/com/spinyowl/spinygui/demo/small.html diff --git a/demo.simple/.gitignore b/spinygui.demo.simple/.gitignore similarity index 100% rename from demo.simple/.gitignore rename to spinygui.demo.simple/.gitignore diff --git a/demo.simple/build.gradle.kts b/spinygui.demo.simple/build.gradle.kts similarity index 100% rename from demo.simple/build.gradle.kts rename to spinygui.demo.simple/build.gradle.kts diff --git a/demo.simple/src/main/java/com/spinyowl/spinygui/demo/simple/Main.java b/spinygui.demo.simple/src/main/java/com/spinyowl/spinygui/demo/simple/Main.java similarity index 100% rename from demo.simple/src/main/java/com/spinyowl/spinygui/demo/simple/Main.java rename to spinygui.demo.simple/src/main/java/com/spinyowl/spinygui/demo/simple/Main.java diff --git a/demo.simple/src/main/java/com/spinyowl/spinygui/demo/simple/OtherMain.java b/spinygui.demo.simple/src/main/java/com/spinyowl/spinygui/demo/simple/OtherMain.java similarity index 100% rename from demo.simple/src/main/java/com/spinyowl/spinygui/demo/simple/OtherMain.java rename to spinygui.demo.simple/src/main/java/com/spinyowl/spinygui/demo/simple/OtherMain.java diff --git a/demo.simple/src/main/java/module-info.java b/spinygui.demo.simple/src/main/java/module-info.java similarity index 100% rename from demo.simple/src/main/java/module-info.java rename to spinygui.demo.simple/src/main/java/module-info.java diff --git a/demo.simple/src/main/resources/com/spinyowl/spinygui/demo/index.html b/spinygui.demo.simple/src/main/resources/com/spinyowl/spinygui/demo/index.html similarity index 100% rename from demo.simple/src/main/resources/com/spinyowl/spinygui/demo/index.html rename to spinygui.demo.simple/src/main/resources/com/spinyowl/spinygui/demo/index.html diff --git a/spinygui/build.gradle.kts b/spinygui/build.gradle.kts index e79f5bc8..d52969bd 100644 --- a/spinygui/build.gradle.kts +++ b/spinygui/build.gradle.kts @@ -3,9 +3,9 @@ plugins { } dependencies { - api(project(":core")) - api(project(":core.backend")) - api(project(":core.backend.lwjgl.nanovg")) + api(project(":spinygui.core")) + api(project(":spinygui.core.backend")) + api(project(":spinygui.core.backend.lwjgl.nanovg")) api(libs.cbchain) } From 6ff15bb4de73ba8b7aa9026891caa44d854f8687 Mon Sep 17 00:00:00 2001 From: SpinyOwl Date: Wed, 27 May 2026 20:40:01 +0200 Subject: [PATCH 017/121] Fix FlexLayout pixel length handling --- .../spinygui/core/layout/impl/FlexLayout.java | 32 +-- .../core/layout/impl/FlexLayoutTest.java | 203 ++++++++++++++++++ .../impl/InlineFormattingContextTest.java | 2 +- .../property/TextPropertyProviderTest.java | 14 +- 4 files changed, 227 insertions(+), 24 deletions(-) create mode 100644 spinygui.core/src/test/java/com/spinyowl/spinygui/core/layout/impl/FlexLayoutTest.java diff --git a/spinygui.core/src/main/java/com/spinyowl/spinygui/core/layout/impl/FlexLayout.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/layout/impl/FlexLayout.java index 8fc28b7e..e956aac5 100644 --- a/spinygui.core/src/main/java/com/spinyowl/spinygui/core/layout/impl/FlexLayout.java +++ b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/layout/impl/FlexLayout.java @@ -437,13 +437,13 @@ private static void setMargin(long node, ResolvedStyle style) { public static void setLength( Length l, long node, - ObjIntConsumer pixelConsumer, + BiConsumer pixelConsumer, BiConsumer percentConsumer) { if (l != null) { if (l instanceof PixelLength) { - pixelConsumer.accept(node, (Integer) l.value()); + pixelConsumer.accept(node, l.value().floatValue()); } else if (l instanceof PercentLength) { - percentConsumer.accept(node, (Float) l.value()); + percentConsumer.accept(node, l.value().floatValue()); } } } @@ -452,21 +452,21 @@ public static void setLength( Length l, long node, int side, - TriConsumer pixelConsumer, + TriConsumer pixelConsumer, TriConsumer percentConsumer) { if (l != null) { if (l instanceof PixelLength) { - pixelConsumer.accept(node, side, (Integer) l.value()); + pixelConsumer.accept(node, side, l.value().floatValue()); } else if (l instanceof PercentLength) { - percentConsumer.accept(node, side, (Float) l.value()); + percentConsumer.accept(node, side, l.value().floatValue()); } } } public static void setLength( - Length l, long node, int side, TriConsumer pixelConsumer) { + Length l, long node, int side, TriConsumer pixelConsumer) { if (l instanceof PixelLength) { - pixelConsumer.accept(node, side, (Integer) l.value()); + pixelConsumer.accept(node, side, l.value().floatValue()); } } @@ -474,7 +474,7 @@ public static void setUnit( Unit unit, long node, int side, - TriConsumer pixelConsumer, + TriConsumer pixelConsumer, TriConsumer percentConsumer) { if (unit != null && !unit.isAuto()) { applyPixelOrPercentToSide(unit, node, side, pixelConsumer, percentConsumer); @@ -485,7 +485,7 @@ public static void setUnit( Unit unit, long node, LongConsumer autoConsumer, - ObjIntConsumer pixelConsumer, + BiConsumer pixelConsumer, BiConsumer percentConsumer) { if (unit != null) { if (unit.isAuto()) { @@ -493,9 +493,9 @@ public static void setUnit( } else { Length l = unit.asLength(); if (l instanceof PixelLength) { - pixelConsumer.accept(node, (Integer) l.value()); + pixelConsumer.accept(node, l.value().floatValue()); } else if (l instanceof PercentLength) { - percentConsumer.accept(node, (Float) l.value()); + percentConsumer.accept(node, l.value().floatValue()); } } } @@ -505,13 +505,13 @@ public static void applyPixelOrPercentToSide( Unit unit, long node, int side, - TriConsumer pixelConsumer, + TriConsumer pixelConsumer, TriConsumer percentConsumer) { Length l = unit.asLength(); if (l instanceof PixelLength) { - pixelConsumer.accept(node, side, (Integer) l.value()); + pixelConsumer.accept(node, side, l.value().floatValue()); } else if (l instanceof PercentLength) { - percentConsumer.accept(node, side, (Float) l.value()); + percentConsumer.accept(node, side, l.value().floatValue()); } } @@ -520,7 +520,7 @@ public static void setUnit( long node, int side, ObjIntConsumer autoConsumer, - TriConsumer pixelConsumer, + TriConsumer pixelConsumer, TriConsumer percentConsumer) { if (unit != null) { if (unit.isAuto()) { diff --git a/spinygui.core/src/test/java/com/spinyowl/spinygui/core/layout/impl/FlexLayoutTest.java b/spinygui.core/src/test/java/com/spinyowl/spinygui/core/layout/impl/FlexLayoutTest.java new file mode 100644 index 00000000..182cc313 --- /dev/null +++ b/spinygui.core/src/test/java/com/spinyowl/spinygui/core/layout/impl/FlexLayoutTest.java @@ -0,0 +1,203 @@ +package com.spinyowl.spinygui.core.layout.impl; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; + +import com.spinyowl.spinygui.core.event.processor.EventProcessor; +import com.spinyowl.spinygui.core.layout.LayoutContext; +import com.spinyowl.spinygui.core.layout.LayoutService; +import com.spinyowl.spinygui.core.node.Element; +import com.spinyowl.spinygui.core.node.Frame; +import com.spinyowl.spinygui.core.node.Node; +import com.spinyowl.spinygui.core.node.NodeBuilder; +import com.spinyowl.spinygui.core.style.ResolvedStyle; +import com.spinyowl.spinygui.core.style.types.Display; +import com.spinyowl.spinygui.core.style.types.Position; +import com.spinyowl.spinygui.core.style.types.border.BorderStyle; +import com.spinyowl.spinygui.core.style.types.flex.AlignItems; +import com.spinyowl.spinygui.core.style.types.flex.AlignSelf; +import com.spinyowl.spinygui.core.style.types.flex.FlexDirection; +import com.spinyowl.spinygui.core.style.types.flex.FlexWrap; +import com.spinyowl.spinygui.core.style.types.flex.JustifyContent; +import com.spinyowl.spinygui.core.style.types.length.Length; +import com.spinyowl.spinygui.core.style.types.length.Unit; +import com.spinyowl.spinygui.core.system.event.processor.SystemEventProcessor; +import com.spinyowl.spinygui.core.time.TimeService; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; +import lombok.NonNull; +import org.junit.jupiter.api.Test; + +class FlexLayoutTest { + + @Test + void setLength_whenPixelLengthHasIntegerLookingValue_passesFloatValue() { + AtomicReference value = new AtomicReference<>(); + + FlexLayout.setLength( + Length.pixel(620), 11L, (node, pixel) -> value.set(pixel), (node, percent) -> {}); + + assertEquals(620f, value.get()); + } + + @Test + void setLength_whenPixelLengthHasFractionalValue_passesFloatValue() { + AtomicReference value = new AtomicReference<>(); + + FlexLayout.setLength( + Length.pixel(12.5f), 11L, (node, pixel) -> value.set(pixel), (node, percent) -> {}); + + assertEquals(12.5f, value.get()); + } + + @Test + void setLengthWithSide_whenPixelLengthIsUsed_passesFloatValue() { + AtomicReference value = new AtomicReference<>(); + AtomicReference side = new AtomicReference<>(); + + FlexLayout.setLength( + Length.pixel(7), + 11L, + 3, + (node, edge, pixel) -> { + side.set(edge); + value.set(pixel); + }, + (node, edge, percent) -> {}); + + assertEquals(3, side.get()); + assertEquals(7f, value.get()); + } + + @Test + void setBorderLength_whenPixelLengthIsUsed_passesFloatValue() { + AtomicReference value = new AtomicReference<>(); + + FlexLayout.setLength(Length.pixel(2.25f), 11L, 3, (node, edge, pixel) -> value.set(pixel)); + + assertEquals(2.25f, value.get()); + } + + @Test + void setUnit_whenPixelLengthIsUsed_passesFloatValue() { + AtomicReference value = new AtomicReference<>(); + AtomicBoolean autoCalled = new AtomicBoolean(false); + + FlexLayout.setUnit( + Length.pixel(34), + 11L, + node -> autoCalled.set(true), + (node, pixel) -> value.set(pixel), + (node, percent) -> {}); + + assertFalse(autoCalled.get()); + assertEquals(34f, value.get()); + } + + @Test + void applyPixelOrPercentToSide_whenPixelLengthIsUsed_passesFloatValue() { + AtomicReference value = new AtomicReference<>(); + + FlexLayout.applyPixelOrPercentToSide( + Length.pixel(12.5f), + 11L, + 3, + (node, edge, pixel) -> value.set(pixel), + (node, edge, percent) -> {}); + + assertEquals(12.5f, value.get()); + } + + @Test + void layout_whenFlexNodeUsesPixelLengths_doesNotThrow() { + Frame parent = NodeBuilder.frame(); + Element child = NodeBuilder.div(); + parent.addChild(child); + + style(parent); + style(child); + parent.box().contentSize(620, 34); + parent.resolvedStyle().display(Display.FLEX); + parent.resolvedStyle().width(Length.pixel(620)); + parent.resolvedStyle().height(Length.pixel(34)); + parent.resolvedStyle().paddingTop(Length.pixel(7)); + parent.resolvedStyle().paddingRight(Length.pixel(7)); + parent.resolvedStyle().paddingBottom(Length.pixel(7)); + parent.resolvedStyle().paddingLeft(Length.pixel(7)); + child.resolvedStyle().width(Length.pixel(26.5f)); + child.resolvedStyle().height(Length.pixel(10)); + + LayoutService layoutService = new NoopLayoutService(); + BlockLayout blockLayout = mock(BlockLayout.class); + doAnswer(invocation -> null) + .when(blockLayout) + .layout(eq(parent), eq(true), any(LayoutContext.class)); + FlexLayout layout = + new FlexLayout( + mock(SystemEventProcessor.class), + mock(EventProcessor.class), + mock(TimeService.class), + blockLayout, + layoutService); + + layout.layout(parent, new LayoutContext()); + + assertTrue(child.box().content().width() > 0); + } + + private static void style(Element element) { + ResolvedStyle style = element.resolvedStyle(); + style.display(Display.BLOCK); + style.position(Position.STATIC); + style.width(Unit.AUTO); + style.height(Unit.AUTO); + style.minWidth(null); + style.maxWidth(null); + style.minHeight(null); + style.maxHeight(null); + style.top(Unit.AUTO); + style.right(Unit.AUTO); + style.bottom(Unit.AUTO); + style.left(Unit.AUTO); + style.paddingTop(Length.ZERO); + style.paddingRight(Length.ZERO); + style.paddingBottom(Length.ZERO); + style.paddingLeft(Length.ZERO); + style.marginTop(Length.ZERO); + style.marginRight(Length.ZERO); + style.marginBottom(Length.ZERO); + style.marginLeft(Length.ZERO); + style.borderTopWidth(Length.pixel(0)); + style.borderRightWidth(Length.pixel(0)); + style.borderBottomWidth(Length.pixel(0)); + style.borderLeftWidth(Length.pixel(0)); + style.borderTopStyle(BorderStyle.NONE); + style.borderRightStyle(BorderStyle.NONE); + style.borderBottomStyle(BorderStyle.NONE); + style.borderLeftStyle(BorderStyle.NONE); + style.flexBasis(Unit.AUTO); + style.flexDirection(FlexDirection.ROW); + style.flexWrap(FlexWrap.NOWRAP); + style.flexGrow(0); + style.flexShrink(0); + style.justifyContent(JustifyContent.FLEX_START); + style.alignItems(AlignItems.FLEX_START); + style.alignSelf(AlignSelf.AUTO); + } + + private static class NoopLayoutService implements LayoutService { + @Override + public void layout(@NonNull Frame frame) {} + + @Override + public void layoutNode(@NonNull Node node, @NonNull LayoutContext context) {} + + @Override + public void layoutChildNodes(@NonNull Element element, @NonNull LayoutContext context) {} + } +} diff --git a/spinygui.core/src/test/java/com/spinyowl/spinygui/core/layout/impl/InlineFormattingContextTest.java b/spinygui.core/src/test/java/com/spinyowl/spinygui/core/layout/impl/InlineFormattingContextTest.java index 30e30f9b..0bb07b3f 100644 --- a/spinygui.core/src/test/java/com/spinyowl/spinygui/core/layout/impl/InlineFormattingContextTest.java +++ b/spinygui.core/src/test/java/com/spinyowl/spinygui/core/layout/impl/InlineFormattingContextTest.java @@ -413,7 +413,7 @@ private void style(Element element, Display display) { private void applyStyleProperty(Element element, String name, String value) { Property property = new TextPropertyProvider().getProperties().stream() - .filter(candidate -> name.equals(candidate.getName())) + .filter(candidate -> name.equals(candidate.name())) .findFirst() .orElseThrow(); property.apply(element, new TermIdent(value)); diff --git a/spinygui.core/src/test/java/com/spinyowl/spinygui/core/style/stylesheet/property/TextPropertyProviderTest.java b/spinygui.core/src/test/java/com/spinyowl/spinygui/core/style/stylesheet/property/TextPropertyProviderTest.java index f5de04c5..1677cf21 100644 --- a/spinygui.core/src/test/java/com/spinyowl/spinygui/core/style/stylesheet/property/TextPropertyProviderTest.java +++ b/spinygui.core/src/test/java/com/spinyowl/spinygui/core/style/stylesheet/property/TextPropertyProviderTest.java @@ -53,10 +53,10 @@ void wordWrap_updatesOverflowWrap() { void wordWrap_acceptsOverflowWrapValues() { Property property = property(WORD_WRAP); - assertTrue(property.getValidator().test(new TermIdent("normal"))); - assertTrue(property.getValidator().test(new TermIdent("break-word"))); - assertTrue(property.getValidator().test(new TermIdent("anywhere"))); - assertFalse(property.getValidator().test(new TermIdent("break-all"))); + assertTrue(property.validator().test(new TermIdent("normal"))); + assertTrue(property.validator().test(new TermIdent("break-word"))); + assertTrue(property.validator().test(new TermIdent("anywhere"))); + assertFalse(property.validator().test(new TermIdent("break-all"))); } @Test @@ -64,13 +64,13 @@ void wordWrap_doesNotReplaceOverflowWrapPropertyRegistration() { Property overflowWrap = property(OVERFLOW_WRAP); Property wordWrap = property(WORD_WRAP); - assertEquals(OVERFLOW_WRAP, overflowWrap.getName()); - assertEquals(WORD_WRAP, wordWrap.getName()); + assertEquals(OVERFLOW_WRAP, overflowWrap.name()); + assertEquals(WORD_WRAP, wordWrap.name()); } private Property property(String name) { return new TextPropertyProvider().getProperties().stream() - .filter(property -> name.equals(property.getName())) + .filter(property -> name.equals(property.name())) .findFirst() .orElseThrow(); } From 619c7adc411373635479f3691a625e8ab09b3584 Mon Sep 17 00:00:00 2001 From: SpinyOwl Date: Thu, 28 May 2026 13:47:38 +0200 Subject: [PATCH 018/121] Improve NodeBuilder attribute helpers --- .../spinygui/core/node/NodeBuilder.java | 122 ++++++++++++++++++ .../spinygui/core/node/NodeBuilderTest.java | 64 +++++++++ 2 files changed, 186 insertions(+) create mode 100644 spinygui.core/src/test/java/com/spinyowl/spinygui/core/node/NodeBuilderTest.java diff --git a/spinygui.core/src/main/java/com/spinyowl/spinygui/core/node/NodeBuilder.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/node/NodeBuilder.java index f3ec75cd..30d54b9d 100644 --- a/spinygui.core/src/main/java/com/spinyowl/spinygui/core/node/NodeBuilder.java +++ b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/node/NodeBuilder.java @@ -1,6 +1,9 @@ package com.spinyowl.spinygui.core.node; +import java.util.Collections; +import java.util.LinkedHashMap; import java.util.Map; +import java.util.Objects; public final class NodeBuilder { @@ -40,6 +43,59 @@ public final class NodeBuilder { private NodeBuilder() {} + public record Attribute(String name, String value) { + public Attribute { + Objects.requireNonNull(name); + Objects.requireNonNull(value); + } + } + + public record Attributes(Map values) { + public Attributes { + Objects.requireNonNull(values); + values = Collections.unmodifiableMap(new LinkedHashMap<>(values)); + } + } + + public static Attribute attr(String name, String value) { + return new Attribute(name, value); + } + + public static Attributes attrs(Attribute... attributes) { + Objects.requireNonNull(attributes); + + Map values = new LinkedHashMap<>(); + for (Attribute attribute : attributes) { + Objects.requireNonNull(attribute); + values.put(attribute.name(), attribute.value()); + } + return new Attributes(values); + } + + public static Attribute id(String value) { + return attr("id", value); + } + + public static Attribute cssClass(String value) { + return attr("class", value); + } + + public static Attribute style(String value) { + return attr("style", value); + } + + public static Attribute name(String value) { + return attr(ATTR_NAME, value); + } + + public static Attribute type(String value) { + return attr(ATTR_TYPE, value); + } + + public static Attribute value(String value) { + return attr(ATTR_VALUE, value); + } + /** * Creates frame with provided child nodes. * @@ -65,6 +121,17 @@ public static Frame frame(Map attributes, Node... nodes) { return addAttributes(frame(nodes), attributes); } + /** + * Creates frame with specified attributes and child nodes. + * + * @param attributes attributes to add. + * @param nodes child nodes to add. + * @return frame with specified attributes and child nodes. + */ + public static Frame frame(Attributes attributes, Node... nodes) { + return addAttributes(frame(nodes), attributes); + } + /** * Creates button element with provided child nodes. * @@ -92,6 +159,17 @@ public static Element button(Map attributes, Node... nodes) { return addAttributes(button(nodes), attributes); } + /** + * Creates button with specified attributes and child nodes. + * + * @param attributes attributes to add. + * @param nodes child nodes to add. + * @return button with specified attributes and child nodes. + */ + public static Element button(Attributes attributes, Node... nodes) { + return addAttributes(button(nodes), attributes); + } + /** * Creates text node with specified content. * @@ -121,6 +199,16 @@ public static EmptyElement input(Map attributes) { return addAttributes(new EmptyElement(NODE_INPUT), attributes); } + /** + * Creates input element with specified attributes. + * + * @param attributes attributes to add. + * @return input element with specified attributes. + */ + public static EmptyElement input(Attributes attributes) { + return addAttributes(new EmptyElement(NODE_INPUT), attributes); + } + /** * Creates input element with specified type attribute value. * @@ -192,6 +280,17 @@ public static Element label(Map attributes, Node... nodes) { return addAttributes(label(nodes), attributes); } + /** + * Creates label with specified attributes and child nodes. + * + * @param attributes attributes to add. + * @param nodes child nodes to add. + * @return label with specified attributes and child nodes. + */ + public static Element label(Attributes attributes, Node... nodes) { + return addAttributes(label(nodes), attributes); + } + /** * Creates div element with provided child nodes. * @@ -229,6 +328,17 @@ public static Element div(Map attributes, Node... nodes) { return addAttributes(div(nodes), attributes); } + /** + * Creates div with specified attributes and child nodes. + * + * @param attributes attributes to add. + * @param nodes child nodes to add. + * @return div with specified attributes and child nodes. + */ + public static Element div(Attributes attributes, Node... nodes) { + return addAttributes(div(nodes), attributes); + } + /** * Creates input with {@code type="radio-button"} and with specified name and value attributes. * @@ -252,4 +362,16 @@ public static T addAttributes(T node, Map attri node.attributes().putAll(attributes); return node; } + + /** + * Used to add attributes to provided node. Allows to chain calls. + * + * @param node node to which attributes should be added. + * @param attributes attributes to add to node. + * @param type of node. + * @return returns filled node. + */ + public static T addAttributes(T node, Attributes attributes) { + return addAttributes(node, attributes.values()); + } } diff --git a/spinygui.core/src/test/java/com/spinyowl/spinygui/core/node/NodeBuilderTest.java b/spinygui.core/src/test/java/com/spinyowl/spinygui/core/node/NodeBuilderTest.java new file mode 100644 index 00000000..331003b2 --- /dev/null +++ b/spinygui.core/src/test/java/com/spinyowl/spinygui/core/node/NodeBuilderTest.java @@ -0,0 +1,64 @@ +package com.spinyowl.spinygui.core.node; + +import static com.spinyowl.spinygui.core.node.NodeBuilder.TYPE_PASSWORD; +import static com.spinyowl.spinygui.core.node.NodeBuilder.attr; +import static com.spinyowl.spinygui.core.node.NodeBuilder.attrs; +import static com.spinyowl.spinygui.core.node.NodeBuilder.cssClass; +import static com.spinyowl.spinygui.core.node.NodeBuilder.div; +import static com.spinyowl.spinygui.core.node.NodeBuilder.id; +import static com.spinyowl.spinygui.core.node.NodeBuilder.input; +import static com.spinyowl.spinygui.core.node.NodeBuilder.name; +import static com.spinyowl.spinygui.core.node.NodeBuilder.style; +import static com.spinyowl.spinygui.core.node.NodeBuilder.type; +import static com.spinyowl.spinygui.core.node.NodeBuilder.value; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.Map; +import org.junit.jupiter.api.Test; + +class NodeBuilderTest { + + @Test + void attrsHelpersCreateElementAttributes() { + Element element = div(attrs(id("metric"), cssClass("diagnostics-metric"), style("color: red"))); + + assertEquals("metric", element.getAttribute("id")); + assertEquals("diagnostics-metric", element.getAttribute("class")); + assertEquals("color: red", element.getAttribute("style")); + } + + @Test + void mapAttributesStillWork() { + Element element = div(Map.of("id", "metric", "class", "diagnostics-metric")); + + assertEquals("metric", element.getAttribute("id")); + assertEquals("diagnostics-metric", element.getAttribute("class")); + } + + @Test + void duplicateAttributesUseLastValue() { + Element element = div(attrs(attr("id", "first"), attr("id", "second"))); + + assertEquals("second", element.getAttribute("id")); + } + + @Test + void attrRejectsNullName() { + assertThrows(NullPointerException.class, () -> attr(null, "value")); + } + + @Test + void attrRejectsNullValue() { + assertThrows(NullPointerException.class, () -> attr("id", null)); + } + + @Test + void inputAcceptsAttributesHelper() { + EmptyElement element = input(attrs(type(TYPE_PASSWORD), name("password"), value("secret"))); + + assertEquals(TYPE_PASSWORD, element.getAttribute("type")); + assertEquals("password", element.getAttribute("name")); + assertEquals("secret", element.getAttribute("value")); + } +} From 765abe60a170e92c5781e80348f66b854baefe04 Mon Sep 17 00:00:00 2001 From: SpinyOwl Date: Thu, 28 May 2026 14:08:58 +0200 Subject: [PATCH 019/121] Reduce inline style ruleset parsing allocations --- .../core/style/manager/StyleManagerImpl.java | 67 +++-- .../style/manager/StyleManagerImplTest.java | 257 ++++++++++++++++++ 2 files changed, 308 insertions(+), 16 deletions(-) create mode 100644 spinygui.core/src/test/java/com/spinyowl/spinygui/core/style/manager/StyleManagerImplTest.java diff --git a/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/manager/StyleManagerImpl.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/manager/StyleManagerImpl.java index cf18650b..d6ddbc6c 100644 --- a/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/manager/StyleManagerImpl.java +++ b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/manager/StyleManagerImpl.java @@ -9,10 +9,12 @@ import com.spinyowl.spinygui.core.style.stylesheet.PropertyStore; import com.spinyowl.spinygui.core.style.stylesheet.Ruleset; import com.spinyowl.spinygui.core.style.stylesheet.StyleSheet; +import com.spinyowl.spinygui.core.style.stylesheet.selector.Selector; import com.spinyowl.spinygui.core.style.stylesheet.selector.simple.AllSelector; import com.spinyowl.spinygui.core.style.stylesheet.selector.simple.ElementSelector; import java.util.ArrayList; import java.util.IdentityHashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; @@ -24,13 +26,23 @@ @RequiredArgsConstructor public class StyleManagerImpl implements StyleManager { + private static final int INLINE_STYLE_RULESET_CACHE_SIZE = 1024; + private static final List INLINE_STYLE_SELECTORS = + List.of(new ElementSelector("element")); private static final Ruleset EMPTY_RULE_SET = - new Ruleset(List.of(new ElementSelector("element")), List.of()); + new Ruleset(INLINE_STYLE_SELECTORS, List.of()); @NonNull private final PropertyStore propertyStore; @NonNull private final StyleSheetParser styleSheetParser; private final Map elementStyleDataMap = new IdentityHashMap<>(); + private final Map inlineStyleRulesetCache = + new LinkedHashMap<>(INLINE_STYLE_RULESET_CACHE_SIZE, 0.75f, true) { + @Override + protected boolean removeEldestEntry(Map.Entry eldest) { + return size() > INLINE_STYLE_RULESET_CACHE_SIZE; + } + }; private List properties; private Ruleset defaultRuleset; @@ -63,23 +75,22 @@ private void updateStyles(Element element, List styleSheets) { } private Ruleset elementStyleRuleSet(Element element) { + String style = element.style(); + if (StringUtils.isBlank(style)) { + elementStyleDataMap.remove(element); + return EMPTY_RULE_SET; + } + StyleData styleData = elementStyleDataMap.computeIfAbsent(element, e -> new StyleData()); Ruleset ruleSet; - if (!Objects.equals(styleData.style(), element.style()) || styleData.styleRuleset() == null) { - try { - String style = element.style(); - List declarations; - if (StringUtils.isBlank(style)) { - declarations = List.of(); - } else { - declarations = styleSheetParser.parseDeclarations(style); - } - ruleSet = new Ruleset(List.of(new ElementSelector("element")), declarations); - - } catch (ParseException e) { - ruleSet = styleData.styleRuleset == null ? EMPTY_RULE_SET : styleData.styleRuleset; - } - styleData.style(element.style()); + if (!Objects.equals(styleData.style(), style) || styleData.styleRuleset() == null) { + InlineStyleParseResult parseResult = + inlineStyleRulesetCache.computeIfAbsent(style, this::parseInlineStyleRuleSet); + ruleSet = + parseResult.successful() + ? parseResult.ruleset() + : previousOrEmptyInlineRuleSet(styleData); + styleData.style(style); styleData.styleRuleset(ruleSet); } else { ruleSet = styleData.styleRuleset; @@ -88,6 +99,19 @@ private Ruleset elementStyleRuleSet(Element element) { return ruleSet; } + private Ruleset previousOrEmptyInlineRuleSet(StyleData styleData) { + return styleData.styleRuleset == null ? EMPTY_RULE_SET : styleData.styleRuleset; + } + + private InlineStyleParseResult parseInlineStyleRuleSet(String style) { + try { + List declarations = styleSheetParser.parseDeclarations(style); + return InlineStyleParseResult.success(new Ruleset(INLINE_STYLE_SELECTORS, declarations)); + } catch (ParseException e) { + return InlineStyleParseResult.failure(); + } + } + public Ruleset defaultRuleset() { List propertyStoreProperties = propertyStore.getProperties(); if (properties == null || !properties.equals(propertyStoreProperties)) { @@ -106,4 +130,15 @@ private static class StyleData { private String style; private Ruleset styleRuleset; } + + private record InlineStyleParseResult(boolean successful, Ruleset ruleset) { + + private static InlineStyleParseResult success(Ruleset ruleset) { + return new InlineStyleParseResult(true, ruleset); + } + + private static InlineStyleParseResult failure() { + return new InlineStyleParseResult(false, null); + } + } } diff --git a/spinygui.core/src/test/java/com/spinyowl/spinygui/core/style/manager/StyleManagerImplTest.java b/spinygui.core/src/test/java/com/spinyowl/spinygui/core/style/manager/StyleManagerImplTest.java new file mode 100644 index 00000000..fb7d48e5 --- /dev/null +++ b/spinygui.core/src/test/java/com/spinyowl/spinygui/core/style/manager/StyleManagerImplTest.java @@ -0,0 +1,257 @@ +package com.spinyowl.spinygui.core.style.manager; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.spinyowl.spinygui.core.node.Element; +import com.spinyowl.spinygui.core.node.Frame; +import com.spinyowl.spinygui.core.parser.StyleSheetParser; +import com.spinyowl.spinygui.core.parser.impl.ParseException; +import com.spinyowl.spinygui.core.parser.impl.StyleSheetParserFactory; +import com.spinyowl.spinygui.core.style.stylesheet.Declaration; +import com.spinyowl.spinygui.core.style.stylesheet.Property; +import com.spinyowl.spinygui.core.style.stylesheet.PropertyStore; +import com.spinyowl.spinygui.core.style.stylesheet.Ruleset; +import com.spinyowl.spinygui.core.style.stylesheet.StyleSheet; +import com.spinyowl.spinygui.core.style.stylesheet.impl.DefaultPropertyStore; +import com.spinyowl.spinygui.core.style.stylesheet.impl.DefaultPropertyStoreProvider; +import com.spinyowl.spinygui.core.style.stylesheet.term.TermIdent; +import com.spinyowl.spinygui.core.style.types.Color; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.junit.jupiter.api.Test; + +class StyleManagerImplTest { + + private static final String TEST_PROPERTY_NAME = "test-property"; + private static final Property TEST_PROPERTY = + Property.builder() + .name(TEST_PROPERTY_NAME) + .defaultValue(new TermIdent("default")) + .updater((term, styles) -> styles.put(TEST_PROPERTY_NAME, term.value())) + .validator(term -> true) + .build(); + + @Test + void recalculateParsesSameElementInlineStyleOnlyOnce() { + CountingStyleSheetParser parser = new CountingStyleSheetParser(); + StyleManager styleManager = new StyleManagerImpl(emptyPropertyStore(), parser); + Frame frame = new Frame(); + Element child = new Element("div"); + child.style("border-color: cyan"); + frame.addChild(child); + + styleManager.recalculate(frame); + styleManager.recalculate(frame); + styleManager.recalculate(frame); + + assertEquals(1, parser.parseCount("border-color: cyan")); + assertEquals(1, parser.totalParseCount()); + } + + @Test + void recalculateSharesParsedRulesetForIdenticalInlineStylesAcrossElements() { + CountingStyleSheetParser parser = new CountingStyleSheetParser(); + StyleManager styleManager = new StyleManagerImpl(emptyPropertyStore(), parser); + Frame frame = new Frame(); + Element first = new Element("div"); + Element second = new Element("span"); + first.style("border-color: cyan"); + second.style("border-color: cyan"); + frame.addChild(first); + frame.addChild(second); + + styleManager.recalculate(frame); + + assertEquals(1, parser.parseCount("border-color: cyan")); + assertEquals(1, parser.totalParseCount()); + } + + @Test + void recalculateDoesNotParseBlankInlineStyles() { + CountingStyleSheetParser parser = new CountingStyleSheetParser(); + StyleManager styleManager = new StyleManagerImpl(emptyPropertyStore(), parser); + Frame frame = new Frame(); + Element emptyStyle = new Element("div"); + Element blankStyle = new Element("span"); + blankStyle.style(" "); + frame.addChild(emptyStyle); + frame.addChild(blankStyle); + + styleManager.recalculate(frame); + styleManager.recalculate(frame); + + assertEquals(0, parser.totalParseCount()); + } + + @Test + void recalculateReusesCachedRulesetWhenInlineStyleChangesBackToKnownValue() { + CountingStyleSheetParser parser = new CountingStyleSheetParser(); + StyleManager styleManager = new StyleManagerImpl(emptyPropertyStore(), parser); + Frame frame = new Frame(); + Element child = new Element("div"); + frame.addChild(child); + + child.style("border-color: cyan"); + styleManager.recalculate(frame); + child.style(" "); + styleManager.recalculate(frame); + child.style("border-color: cyan"); + styleManager.recalculate(frame); + + assertEquals(1, parser.parseCount("border-color: cyan")); + assertEquals(1, parser.totalParseCount()); + } + + @Test + void recalculateCachesFailedInlineStyleParses() { + CountingStyleSheetParser parser = new CountingStyleSheetParser(); + parser.fail("invalid"); + StyleManager styleManager = new StyleManagerImpl(emptyPropertyStore(), parser); + Frame frame = new Frame(); + Element first = new Element("div"); + Element second = new Element("span"); + first.style("invalid"); + second.style("invalid"); + frame.addChild(first); + frame.addChild(second); + + styleManager.recalculate(frame); + styleManager.recalculate(frame); + + assertEquals(1, parser.parseCount("invalid")); + assertEquals(1, parser.totalParseCount()); + } + + @Test + void recalculatePreservesPreviousInlineRulesetWhenChangedStyleFailsToParse() { + CountingStyleSheetParser parser = new CountingStyleSheetParser(); + parser.fail("invalid"); + StyleManager styleManager = new StyleManagerImpl(emptyPropertyStore(), parser); + Frame frame = new Frame(); + Element child = new Element("div"); + frame.addChild(child); + + child.style("valid"); + styleManager.recalculate(frame); + child.style("invalid"); + styleManager.recalculate(frame); + + Declaration inlineDeclaration = lastRuleSet(child).declarations().getFirst(); + assertEquals("valid", inlineDeclaration.term().value()); + assertEquals(1, parser.parseCount("valid")); + assertEquals(1, parser.parseCount("invalid")); + } + + @Test + void recalculateUsesEmptyRulesetWhenInitialInlineStyleFailsToParse() { + CountingStyleSheetParser parser = new CountingStyleSheetParser(); + parser.fail("invalid"); + StyleManager styleManager = new StyleManagerImpl(emptyPropertyStore(), parser); + Frame frame = new Frame(); + Element child = new Element("div"); + child.style("invalid"); + frame.addChild(child); + + styleManager.recalculate(frame); + + assertEquals(0, lastRuleSet(child).declarations().size()); + assertEquals(1, parser.parseCount("invalid")); + } + + @Test + void recalculateEvictsLeastRecentlyUsedInlineStyleRulesets() { + CountingStyleSheetParser parser = new CountingStyleSheetParser(); + StyleManager styleManager = new StyleManagerImpl(emptyPropertyStore(), parser); + Frame frame = new Frame(); + Element child = new Element("div"); + frame.addChild(child); + + child.style("style-0"); + styleManager.recalculate(frame); + for (int i = 1; i <= 1024; i++) { + child.style("style-" + i); + styleManager.recalculate(frame); + } + child.style("style-0"); + styleManager.recalculate(frame); + + assertEquals(2, parser.parseCount("style-0")); + } + + @Test + void recalculateAppliesSharedInlineRulesetParsedByRealParser() { + PropertyStore propertyStore = new DefaultPropertyStoreProvider().createPropertyStore(); + StyleSheetParser parser = StyleSheetParserFactory.createParser(propertyStore); + StyleManager styleManager = new StyleManagerImpl(propertyStore, parser); + Frame frame = new Frame(); + Element first = new Element("div"); + Element second = new Element("span"); + first.style("color: red"); + second.style("color: red"); + frame.addChild(first); + frame.addChild(second); + + styleManager.recalculate(frame); + + assertEquals(Color.RED, first.resolvedStyle().color()); + assertEquals(Color.RED, second.resolvedStyle().color()); + } + + private static PropertyStore emptyPropertyStore() { + return new DefaultPropertyStore(); + } + + private static Ruleset lastRuleSet(Element element) { + List rules = element.resolvedStyle().rules(); + return rules.getLast(); + } + + private static final class CountingStyleSheetParser implements StyleSheetParser { + private final Map parseCounts = new HashMap<>(); + private final Set failures = new HashSet<>(); + + void fail(String style) { + failures.add(style); + } + + int parseCount(String style) { + return parseCounts.getOrDefault(style, 0); + } + + int totalParseCount() { + return parseCounts.values().stream().mapToInt(Integer::intValue).sum(); + } + + @Override + public StyleSheet parse(String css) { + return new StyleSheet(List.of(), List.of()); + } + + @Override + public List parseDeclarations(String css) { + parseCounts.merge(css, 1, Integer::sum); + if (failures.contains(css)) { + throw new ParseException(css); + } + return List.of(new Declaration(TEST_PROPERTY, new TermIdent(css))); + } + + @Override + public String toCss(StyleSheet styleSheet) { + return ""; + } + + @Override + public String toCss(Ruleset ruleSet) { + return ""; + } + + @Override + public String toCss(Declaration declaration) { + return ""; + } + } +} From 62ba4b366a6c2e569e3767a053ad35055a835573 Mon Sep 17 00:00:00 2001 From: SpinyOwl Date: Thu, 28 May 2026 15:05:33 +0200 Subject: [PATCH 020/121] Avoid retaining styled elements in style manager --- .../core/style/manager/StyleManagerImpl.java | 4 ++-- .../style/manager/StyleManagerImplTest.java | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/manager/StyleManagerImpl.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/manager/StyleManagerImpl.java index d6ddbc6c..242cb74b 100644 --- a/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/manager/StyleManagerImpl.java +++ b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/manager/StyleManagerImpl.java @@ -13,11 +13,11 @@ import com.spinyowl.spinygui.core.style.stylesheet.selector.simple.AllSelector; import com.spinyowl.spinygui.core.style.stylesheet.selector.simple.ElementSelector; import java.util.ArrayList; -import java.util.IdentityHashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.WeakHashMap; import lombok.Data; import lombok.NonNull; import lombok.RequiredArgsConstructor; @@ -35,7 +35,7 @@ public class StyleManagerImpl implements StyleManager { @NonNull private final PropertyStore propertyStore; @NonNull private final StyleSheetParser styleSheetParser; - private final Map elementStyleDataMap = new IdentityHashMap<>(); + private final Map elementStyleDataMap = new WeakHashMap<>(); private final Map inlineStyleRulesetCache = new LinkedHashMap<>(INLINE_STYLE_RULESET_CACHE_SIZE, 0.75f, true) { @Override diff --git a/spinygui.core/src/test/java/com/spinyowl/spinygui/core/style/manager/StyleManagerImplTest.java b/spinygui.core/src/test/java/com/spinyowl/spinygui/core/style/manager/StyleManagerImplTest.java index fb7d48e5..d835a4c2 100644 --- a/spinygui.core/src/test/java/com/spinyowl/spinygui/core/style/manager/StyleManagerImplTest.java +++ b/spinygui.core/src/test/java/com/spinyowl/spinygui/core/style/manager/StyleManagerImplTest.java @@ -51,6 +51,23 @@ void recalculateParsesSameElementInlineStyleOnlyOnce() { assertEquals(1, parser.totalParseCount()); } + @Test + void recalculateKeepsPerElementStyleDataForLiveElements() { + CountingStyleSheetParser parser = new CountingStyleSheetParser(); + StyleManager styleManager = new StyleManagerImpl(emptyPropertyStore(), parser); + Frame frame = new Frame(); + Element child = new Element("div"); + child.style("valid"); + frame.addChild(child); + + styleManager.recalculate(frame); + styleManager.recalculate(frame); + + Declaration inlineDeclaration = lastRuleSet(child).declarations().getFirst(); + assertEquals("valid", inlineDeclaration.term().value()); + assertEquals(1, parser.parseCount("valid")); + } + @Test void recalculateSharesParsedRulesetForIdenticalInlineStylesAcrossElements() { CountingStyleSheetParser parser = new CountingStyleSheetParser(); From b575ec5b4ae3806d6543ae5c3c13a254aeaef418 Mon Sep 17 00:00:00 2001 From: SpinyOwl Date: Sat, 30 May 2026 20:38:01 +0200 Subject: [PATCH 021/121] Add overflow style contract --- .../scrollable-overflow-containers.md | 202 ++++++++++++++++++ .../spinygui/core/style/ResolvedStyle.java | 28 +++ .../property/OverflowPropertyProvider.java | 79 +++++++ .../spinygui/core/style/types/Overflow.java | 5 + .../manager/OverflowStyleManagerTest.java | 61 ++++++ .../OverflowPropertyProviderTest.java | 111 ++++++++++ 6 files changed, 486 insertions(+) create mode 100644 docs/features/scrollable-overflow-containers.md create mode 100644 spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/OverflowPropertyProvider.java create mode 100644 spinygui.core/src/test/java/com/spinyowl/spinygui/core/style/manager/OverflowStyleManagerTest.java create mode 100644 spinygui.core/src/test/java/com/spinyowl/spinygui/core/style/stylesheet/property/OverflowPropertyProviderTest.java diff --git a/docs/features/scrollable-overflow-containers.md b/docs/features/scrollable-overflow-containers.md new file mode 100644 index 00000000..4eca2c17 --- /dev/null +++ b/docs/features/scrollable-overflow-containers.md @@ -0,0 +1,202 @@ +# Scrollable Overflow Containers + +## Goal +Support CSS `overflow`, `overflow-x`, and `overflow-y` for block and flex elements with `visible`, `hidden`, `auto`, and `scroll`, preserving constrained element boxes while allowing overflowing child content to affect scroll ranges, render clipping, scroll input, and hit-testing. + +## Non-Goals +- Native scrollbar painting, scrollbar pseudo-element styling, scrollbar layout gutters, or draggable scrollbar thumbs. +- Full CSS overflow edge cases such as `clip`, `overlay`, scroll anchoring, scroll snapping, momentum scrolling, or `scroll-behavior`. +- Inline element overflow behavior beyond descendants inside block/flex scroll containers. +- Reworking existing block/flex layout algorithms except where needed to keep scroll/client metrics correct. + +## Context +- `Element` already stores `scrollTop`, `scrollLeft`, `scrollWidth`, `scrollHeight`, `clientWidth`, and `clientHeight`. +- `LayoutServiceImpl.updateScrollAndClientSize` already computes scroll/client sizes and clamps offsets after layout, but it is not overflow-style aware. +- `Node.absolutePosition()` subtracts `offsetParent.scrollLeft()` and `scrollTop()`, so render positions already account for ancestor scroll offsets once scroll offsets are set. +- `SystemScrollEventListener` currently updates every element under the cursor that has larger scroll size than client size, without checking `overflow-x`/`overflow-y` or scroll chaining limits. +- `NvgRenderUtils.createScissor` currently clips by all offset parents' padding boxes. This is too broad for `overflow: visible` and not explicit enough for scroll-container clipping to content boxes. +- Hit-testing uses `NodeUtilities.getTargetElement*` and `RectangleIntersection`, but does not reject children outside an ancestor scroll container's viewport. + +## Assumptions and Open Questions +- Assumption: `overflow` shorthand should accept one or two identifiers. One value applies to both axes; two values map to x then y. +- Assumption: `overflow: visible` is the default for both axes, matching CSS defaults and avoiding current unconditional clipping. +- Assumption: `hidden`, `auto`, and `scroll` create clipped scroll containers; `hidden` is programmatically scrollable but should not consume wheel input unless already scrolled by code. +- Assumption: scroll deltas should keep the existing `50` pixel multiplier unless a separate input-tuning task changes it. +- Assumption: clipping should use the content box, not the padding box, because the requested behavior explicitly says content box. +- Question: Should focused scroll containers receive wheel input even when the cursor is not over them? The plan treats hovered containers first and focused containers as fallback only when no hovered scroll container can consume the delta. + +## Step-by-Step Plan + +### Step 1: Add Overflow Style Contract +**Purpose:** Make overflow values parseable and available through `ResolvedStyle`. + +**Changes:** +- [x] Add constants and predefined values to `Overflow`: `VISIBLE`, `HIDDEN`, `AUTO`, and `SCROLL`. +- [x] Add `overflow()`, `overflowX()`, and `overflowY()` accessors/mutators to `ResolvedStyle`. +- [x] Add a localized property provider, for example `OverflowPropertyProvider`, that registers `overflow-x` and `overflow-y` as non-inherited longhands with default `visible`. +- [x] Implement `overflow` shorthand expansion for one or two `TermIdent` values, writing longhands into `OVERFLOW_X` and `OVERFLOW_Y`. +- [x] Reject unsupported identifiers through the existing property validator path. + +**Acceptance Checks:** +- [x] Add parser/style tests proving `overflow: hidden`, `overflow: auto scroll`, `overflow-x: auto`, and `overflow-y: scroll` resolve to the expected longhand values. +- [x] Add a negative parser/style test proving an unsupported value such as `overflow: overlay` is not applied. +- [x] Run `.\gradlew.bat :spinygui.core:test` and confirm the new and existing core tests pass. + +**Dependencies:** None. + +**Risks:** Shorthand parsing may need a custom updater instead of existing `Property.put`; keep it contained in the new provider and avoid changing generic property semantics. + +### Step 2: Centralize Overflow Semantics +**Purpose:** Avoid duplicating fragile checks across layout, input, rendering, and hit-testing. + +**Changes:** +- [ ] Add a small utility in core, for example `OverflowUtils`, with methods for `clipsX`, `clipsY`, `clipsAny`, `acceptsWheelX`, `acceptsWheelY`, `maxScrollLeft`, `maxScrollTop`, and `clampScrollOffsets`. +- [ ] Define `auto` and `scroll` as wheel-scrollable only when `scrollWidth > clientWidth` or `scrollHeight > clientHeight`. +- [ ] Define `hidden` as clipped but not wheel-scrollable. +- [ ] Define `visible` as neither clipped nor wheel-scrollable. +- [ ] Replace ad hoc scroll clamping in `LayoutServiceImpl.updateScrollAndClientSize` with the utility while preserving current zero-reset behavior when content fits. + +**Acceptance Checks:** +- [ ] Add unit tests for max scroll and clamping: negative offsets clamp to `0`, oversized offsets clamp to `scrollSize - clientSize`, and fitting content resets offsets to `0`. +- [ ] Add tests proving `visible` does not consume wheel scroll, `hidden` clips without wheel consumption, and `auto`/`scroll` consume only when the axis overflows. +- [ ] Run `.\gradlew.bat :spinygui.core:test`. + +**Dependencies:** Step 1. + +**Risks:** Existing demos may rely on current implicit scroll behavior even without CSS overflow. This is a deliberate behavior change; tests should make the new contract explicit. + +### Step 3: Preserve Layout Boxes While Measuring Overflow +**Purpose:** Ensure fixed or constrained containers keep their own content box while child overflow contributes to scroll ranges. + +**Changes:** +- [ ] Audit `BlockLayout.childrenHeight` and `FlexLayout.layout` to verify explicit `height`, `max-height`, and flex-assigned heights preserve the container content size even when children exceed it. +- [ ] Adjust scroll size calculation in `LayoutServiceImpl.updateScrollAndClientSize` so child margin boxes beyond the content box increase `scrollWidth`/`scrollHeight`, but parent `clientWidth`/`clientHeight` remain the laid-out content size. +- [ ] Use layout children or actual child nodes consistently for scroll-size calculation; document and test the choice for positioned children. Initial recommendation: keep absolute-positioned descendants out of normal scroll-size expansion to match current `affectsScrollSize`. +- [ ] Clamp offsets after every layout pass using the centralized utility. + +**Acceptance Checks:** +- [ ] Add a block layout test where a `height: 100px; overflow-y: auto` container has a 300px-tall child: container `clientHeight` remains `100`, `scrollHeight` becomes at least `300`, and `scrollTop` clamps to `scrollHeight - clientHeight`. +- [ ] Add a flex layout test where a flex child overflows its allocated height: the flex item content box remains the Yoga-assigned size and scroll range reflects child overflow. +- [ ] Add a test that `overflow: visible` preserves the same layout box and scroll metrics but is not wheel-scrollable. +- [ ] Run `.\gradlew.bat :spinygui.core:test`. + +**Dependencies:** Steps 1 and 2. + +**Risks:** Flex layout currently lays out children before Yoga applies final child sizes. If scroll metrics are stale for flex children, run scroll-size updates after final Yoga positions and sizes, not during child layout. + +### Step 4: Implement Scroll Event Consumption and Chaining +**Purpose:** Update only the nearest eligible scroll container, and bubble/chains scroll to ancestors only when the current container cannot consume more in that direction. + +**Changes:** +- [ ] Refactor `SystemScrollEventListener` to build a target chain from hovered elements, deepest first. +- [ ] Include `frame.getFocusedElement()` as a fallback chain only when no hovered candidate can consume the event. +- [ ] For each axis, find the deepest candidate whose overflow mode allows wheel scrolling and whose offset can move in the requested direction. +- [ ] Apply delta per axis and clamp through `OverflowUtils`; if one axis is consumed by a child, do not also scroll an ancestor on that axis. +- [ ] Push `ScrollEvent` only for elements whose scroll offset actually changed. +- [ ] Keep existing event offsets in the generated `ScrollEvent` so listeners can see the original wheel delta. + +**Acceptance Checks:** +- [ ] Add listener tests where an inner scroll container consumes vertical scroll and the outer ancestor remains unchanged. +- [ ] Add a test where an inner container at max scroll passes further scroll to the outer container. +- [ ] Add a test where `overflow-y: hidden` does not consume wheel input and an eligible ancestor can scroll. +- [ ] Add a horizontal scroll test using `offsetX`. +- [ ] Run `.\gradlew.bat :spinygui.core:test`. + +**Dependencies:** Steps 1 through 3. + +**Risks:** Existing tests expect a scroll event for every target under the cursor. Update those tests to the new consumption model rather than preserving the old broadcast behavior. + +### Step 5: Add Overflow-Aware Hit-Testing +**Purpose:** Prevent events from reaching content clipped outside a scroll container and make scrolled child positions hit-test correctly. + +**Changes:** +- [ ] Add a helper that checks whether a point is inside every clipping ancestor's content box before accepting an element as a target. +- [ ] Update `NodeUtilities.getTargetElement`, `getTargetElementList`, and recursive helpers to apply the clipping-ancestor check before descending into children. +- [ ] Preserve z-index ordering and `pointer-events` behavior. +- [ ] Use `absolutePosition()` for child position checks so existing offset subtraction applies scroll offsets during hit-testing. + +**Acceptance Checks:** +- [ ] Add a hit-test test where a child visually outside an `overflow: hidden` container is not returned. +- [ ] Add a hit-test test where a child scrolled into view by `scrollTop` is returned at its visible viewport position. +- [ ] Add a nested scroll-container test where the inner clipped viewport limits its descendants independently from the outer container. +- [ ] Run `.\gradlew.bat :spinygui.core:test`. + +**Dependencies:** Steps 1 through 3. + +**Risks:** `visibleInParents` already contains incomplete clipping logic. Prefer replacing or bypassing it with a narrowly tested helper instead of extending the stale TODO path blindly. + +### Step 6: Apply Scroll Container Rendering Clips +**Purpose:** Render scroll-container contents only inside their content box while allowing `overflow: visible` descendants to paint outside ancestors. + +**Changes:** +- [ ] Split NanoVG clipping into an injectable or testable clip helper, for example `NvgClipStack`, so tests can assert scissor/intersect/reset calls without requiring a live OpenGL context. +- [ ] Change `NvgRenderUtils.createScissor` or its replacement so it intersects only ancestors whose overflow clips on at least one axis. +- [ ] Clip children of scroll containers to the element content box; do not clip the element's own background/border to its own content box. +- [ ] Preserve clipping from multiple nested scroll containers by intersecting from root to leaf. +- [ ] Ensure text and inline-fragment render paths use the same clipping helper as element rendering. + +**Acceptance Checks:** +- [ ] Add backend tests with a fake clip sink proving `overflow: visible` ancestors do not emit clip calls. +- [ ] Add backend tests proving `overflow: hidden`, `auto`, and `scroll` ancestors clip to content-box coordinates. +- [ ] Add a nested clipping test proving root-to-leaf intersection order. +- [ ] Run `.\gradlew.bat :spinygui.core.backend.lwjgl.nanovg:test`. + +**Dependencies:** Steps 1, 2, and 5. + +**Risks:** The current renderer clips every element by parent padding boxes, so changing this can reveal content that was previously hidden. This is required for correct `overflow: visible`; include visual/manual verification in Step 8. + +### Step 7: Keep Rendered Child Offsets and Layout Tree Consistent +**Purpose:** Verify scroll offsets affect all render paths, including block children, flex children, text, and inline fragments. + +**Changes:** +- [ ] Audit `Node.absolutePosition()`, `NvgElementRenderer.inlineFormattingOffset`, and `NvgTextRenderer.inlineFormattingOffset` for double-subtraction or missing subtraction of ancestor scroll offsets. +- [ ] Add tests around absolute position calculations for nested scroll parents, including `scrollTop` and `scrollLeft`. +- [ ] Adjust inline rendering offset if inline text inside a scrolled block does not move with the block content. +- [ ] Confirm positioned descendants follow the existing `offsetParent` model and document any limitation for absolute-positioned children inside scroll containers. + +**Acceptance Checks:** +- [ ] Add a unit test where a child under a scrolled parent has `absolutePosition().y == unscrolledY - scrollTop`. +- [ ] Add a test covering nested scroll offsets accumulating from multiple ancestors. +- [ ] Run `.\gradlew.bat :spinygui.core:test`. + +**Dependencies:** Steps 3, 5, and 6. + +**Risks:** Absolute-positioned descendants may use a different offset parent than their DOM parent. Do not silently change that model in this feature unless tests prove it is necessary for scroll correctness. + +### Step 8: Add Demo Coverage and Documentation +**Purpose:** Make the feature easy to manually verify and keep support docs accurate. + +**Changes:** +- [ ] Update `docs/features/css-properties-support.md` to mark `overflow`, `overflow-x`, and `overflow-y` as supported after tests pass. +- [ ] Add or update a demo HTML/CSS resource with nested vertical and horizontal scroll containers. +- [ ] Include one `overflow: visible` case in the demo to catch accidental clipping regressions. + +**Acceptance Checks:** +- [ ] Run the relevant demo and manually verify: vertical scroll, horizontal scroll, nested scroll chaining, clipped content, and visible overflow. +- [ ] Run `.\gradlew.bat test` for full validation. +- [ ] Confirm no unrelated generated files or build artifacts are included in the change. + +**Dependencies:** Steps 1 through 7. + +**Risks:** Manual demo verification depends on the local backend/OpenGL environment. If unavailable, record that limitation and rely on unit/backend tests for CI coverage. + +## Verification Strategy +- After style work: `.\gradlew.bat :spinygui.core:test --tests "*Style*"` +- After layout and hit-test work: `.\gradlew.bat :spinygui.core:test` +- After NanoVG clipping work: `.\gradlew.bat :spinygui.core.backend.lwjgl.nanovg:test` +- Final validation: `.\gradlew.bat test` + +## Review Boundaries +- Step 1 can stand alone as a style parsing/resolved-style change. +- Steps 2 and 3 should be reviewed together only if scroll-size clamping naturally moves into the same utility. +- Step 4 should be a separate event-behavior change because it alters scroll dispatch semantics. +- Step 5 should be a separate hit-testing change with focused tests. +- Steps 6 and 7 can be split if renderer clipping requires a larger helper extraction. +- Step 8 should be documentation/demo-only after behavior is tested. + +## Deferred Work +- Scrollbar rendering and `::-webkit-scrollbar`/`::scrollbar` styling. +- Keyboard scrolling, page scrolling, and touch/drag scrolling. +- `scroll-behavior`, smooth scrolling, scroll snapping, and scroll anchoring. +- Scrollbar gutter effects on layout metrics. +- Support for `overflow: clip` and `overflow-clip-margin`. diff --git a/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/ResolvedStyle.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/ResolvedStyle.java index 17fbc254..92356da3 100644 --- a/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/ResolvedStyle.java +++ b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/ResolvedStyle.java @@ -52,7 +52,10 @@ import static com.spinyowl.spinygui.core.style.stylesheet.Properties.MIN_HEIGHT; import static com.spinyowl.spinygui.core.style.stylesheet.Properties.MIN_WIDTH; import static com.spinyowl.spinygui.core.style.stylesheet.Properties.OPACITY; +import static com.spinyowl.spinygui.core.style.stylesheet.Properties.OVERFLOW; +import static com.spinyowl.spinygui.core.style.stylesheet.Properties.OVERFLOW_X; import static com.spinyowl.spinygui.core.style.stylesheet.Properties.OVERFLOW_WRAP; +import static com.spinyowl.spinygui.core.style.stylesheet.Properties.OVERFLOW_Y; import static com.spinyowl.spinygui.core.style.stylesheet.Properties.PADDING_BOTTOM; import static com.spinyowl.spinygui.core.style.stylesheet.Properties.PADDING_LEFT; import static com.spinyowl.spinygui.core.style.stylesheet.Properties.PADDING_RIGHT; @@ -74,6 +77,7 @@ import com.spinyowl.spinygui.core.style.types.BoxShadow; import com.spinyowl.spinygui.core.style.types.Color; import com.spinyowl.spinygui.core.style.types.Display; +import com.spinyowl.spinygui.core.style.types.Overflow; import com.spinyowl.spinygui.core.style.types.OverflowWrap; import com.spinyowl.spinygui.core.style.types.PointerEvents; import com.spinyowl.spinygui.core.style.types.Position; @@ -697,6 +701,30 @@ public void opacity(Float opacity) { set(OPACITY, opacity); } + public Overflow overflow() { + return get(OVERFLOW); + } + + public void overflow(Overflow overflow) { + set(OVERFLOW, overflow); + } + + public Overflow overflowX() { + return get(OVERFLOW_X); + } + + public void overflowX(Overflow overflow) { + set(OVERFLOW_X, overflow); + } + + public Overflow overflowY() { + return get(OVERFLOW_Y); + } + + public void overflowY(Overflow overflow) { + set(OVERFLOW_Y, overflow); + } + public Float lineHeight() { return get(LINE_HEIGHT); } diff --git a/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/OverflowPropertyProvider.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/OverflowPropertyProvider.java new file mode 100644 index 00000000..4734008f --- /dev/null +++ b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/stylesheet/property/OverflowPropertyProvider.java @@ -0,0 +1,79 @@ +package com.spinyowl.spinygui.core.style.stylesheet.property; + +import static com.spinyowl.spinygui.core.style.stylesheet.Properties.OVERFLOW; +import static com.spinyowl.spinygui.core.style.stylesheet.Properties.OVERFLOW_X; +import static com.spinyowl.spinygui.core.style.stylesheet.Properties.OVERFLOW_Y; +import static com.spinyowl.spinygui.core.style.stylesheet.Property.checkValue; +import static com.spinyowl.spinygui.core.style.stylesheet.Property.put; + +import com.spinyowl.spinygui.core.style.stylesheet.Property; +import com.spinyowl.spinygui.core.style.stylesheet.PropertyProvider; +import com.spinyowl.spinygui.core.style.stylesheet.Term; +import com.spinyowl.spinygui.core.style.stylesheet.term.TermIdent; +import com.spinyowl.spinygui.core.style.stylesheet.term.TermList; +import com.spinyowl.spinygui.core.style.stylesheet.term.TermList.Operator; +import com.spinyowl.spinygui.core.style.types.Overflow; +import java.util.List; +import java.util.Map; + +public class OverflowPropertyProvider implements PropertyProvider { + + private static final Term DEFAULT_VALUE = new TermIdent(Overflow.VISIBLE.name()); + + @Override + public List getProperties() { + return List.of( + Property.builder() + .name(OVERFLOW) + .defaultValue(DEFAULT_VALUE) + .updater(OverflowPropertyProvider::update) + .validator(OverflowPropertyProvider::test) + .shorthand(true) + .build(), + Property.builder() + .name(OVERFLOW_X) + .defaultValue(DEFAULT_VALUE) + .updater(put(OVERFLOW_X, TermIdent.class, Overflow::find)) + .validator(checkValue(TermIdent.class, Overflow::contains)) + .build(), + Property.builder() + .name(OVERFLOW_Y) + .defaultValue(DEFAULT_VALUE) + .updater(put(OVERFLOW_Y, TermIdent.class, Overflow::find)) + .validator(checkValue(TermIdent.class, Overflow::contains)) + .build()); + } + + private static void update(Term term, Map styles) { + List values = values(term); + Overflow overflowX = values.get(0); + Overflow overflowY = values.size() == 1 ? overflowX : values.get(1); + styles.put(OVERFLOW_X, overflowX); + styles.put(OVERFLOW_Y, overflowY); + } + + private static boolean test(Term term) { + if (term instanceof TermIdent termIdent) { + return Overflow.contains(termIdent.value()); + } + if (term instanceof TermList termList) { + return termList.size() >= 1 + && termList.size() <= 2 + && Operator.SPACE.equals(termList.operator()) + && termList.terms().stream().allMatch(OverflowPropertyProvider::test); + } + return false; + } + + private static List values(Term term) { + if (term instanceof TermIdent termIdent) { + return List.of(Overflow.find(termIdent.value())); + } + TermList termList = (TermList) term; + return termList.terms().stream() + .map(TermIdent.class::cast) + .map(TermIdent::value) + .map(Overflow::find) + .toList(); + } +} diff --git a/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/types/Overflow.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/types/Overflow.java index 3ee84766..3a938493 100644 --- a/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/types/Overflow.java +++ b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/style/types/Overflow.java @@ -17,6 +17,11 @@ public final class Overflow { private static final Map VALUES = new HashMap<>(); + public static final Overflow VISIBLE = Overflow.create("visible"); + public static final Overflow HIDDEN = Overflow.create("hidden"); + public static final Overflow AUTO = Overflow.create("auto"); + public static final Overflow SCROLL = Overflow.create("scroll"); + /** Name of Overflow */ @NonNull private final String name; diff --git a/spinygui.core/src/test/java/com/spinyowl/spinygui/core/style/manager/OverflowStyleManagerTest.java b/spinygui.core/src/test/java/com/spinyowl/spinygui/core/style/manager/OverflowStyleManagerTest.java new file mode 100644 index 00000000..0fab2aed --- /dev/null +++ b/spinygui.core/src/test/java/com/spinyowl/spinygui/core/style/manager/OverflowStyleManagerTest.java @@ -0,0 +1,61 @@ +package com.spinyowl.spinygui.core.style.manager; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.spinyowl.spinygui.core.node.Element; +import com.spinyowl.spinygui.core.node.Frame; +import com.spinyowl.spinygui.core.parser.StyleSheetParser; +import com.spinyowl.spinygui.core.parser.impl.StyleSheetParserFactory; +import com.spinyowl.spinygui.core.style.stylesheet.PropertyStore; +import com.spinyowl.spinygui.core.style.stylesheet.impl.DefaultPropertyStoreProvider; +import com.spinyowl.spinygui.core.style.types.Overflow; +import org.junit.jupiter.api.Test; + +class OverflowStyleManagerTest { + + @Test + void recalculateResolvesOverflowShorthandSingleValue() { + Element element = styledElement("overflow: hidden"); + + assertEquals(Overflow.HIDDEN, element.resolvedStyle().overflowX()); + assertEquals(Overflow.HIDDEN, element.resolvedStyle().overflowY()); + } + + @Test + void recalculateResolvesOverflowShorthandTwoValues() { + Element element = styledElement("overflow: auto scroll"); + + assertEquals(Overflow.AUTO, element.resolvedStyle().overflowX()); + assertEquals(Overflow.SCROLL, element.resolvedStyle().overflowY()); + } + + @Test + void recalculateResolvesOverflowLonghands() { + Element element = styledElement("overflow-x: auto; overflow-y: scroll"); + + assertEquals(Overflow.AUTO, element.resolvedStyle().overflowX()); + assertEquals(Overflow.SCROLL, element.resolvedStyle().overflowY()); + } + + @Test + void recalculateRejectsUnsupportedOverflowValue() { + Element element = styledElement("overflow: overlay"); + + assertEquals(Overflow.VISIBLE, element.resolvedStyle().overflowX()); + assertEquals(Overflow.VISIBLE, element.resolvedStyle().overflowY()); + } + + private Element styledElement(String style) { + PropertyStore propertyStore = new DefaultPropertyStoreProvider().createPropertyStore(); + StyleSheetParser parser = StyleSheetParserFactory.createParser(propertyStore); + StyleManager styleManager = new StyleManagerImpl(propertyStore, parser); + Frame frame = new Frame(); + Element element = new Element("div"); + element.style(style); + frame.addChild(element); + + styleManager.recalculate(frame); + + return element; + } +} diff --git a/spinygui.core/src/test/java/com/spinyowl/spinygui/core/style/stylesheet/property/OverflowPropertyProviderTest.java b/spinygui.core/src/test/java/com/spinyowl/spinygui/core/style/stylesheet/property/OverflowPropertyProviderTest.java new file mode 100644 index 00000000..549d373a --- /dev/null +++ b/spinygui.core/src/test/java/com/spinyowl/spinygui/core/style/stylesheet/property/OverflowPropertyProviderTest.java @@ -0,0 +1,111 @@ +package com.spinyowl.spinygui.core.style.stylesheet.property; + +import static com.spinyowl.spinygui.core.style.stylesheet.Properties.OVERFLOW; +import static com.spinyowl.spinygui.core.style.stylesheet.Properties.OVERFLOW_X; +import static com.spinyowl.spinygui.core.style.stylesheet.Properties.OVERFLOW_Y; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.spinyowl.spinygui.core.node.Element; +import com.spinyowl.spinygui.core.node.NodeBuilder; +import com.spinyowl.spinygui.core.style.stylesheet.Property; +import com.spinyowl.spinygui.core.style.stylesheet.term.TermIdent; +import com.spinyowl.spinygui.core.style.stylesheet.term.TermList; +import com.spinyowl.spinygui.core.style.stylesheet.term.TermList.Operator; +import com.spinyowl.spinygui.core.style.types.Overflow; +import org.junit.jupiter.api.Test; + +class OverflowPropertyProviderTest { + + @Test + void overflowX_acceptsSupportedValues() { + Property property = property(OVERFLOW_X); + Element element = NodeBuilder.div(); + + property.apply(element, new TermIdent("auto")); + + assertEquals(Overflow.AUTO, element.resolvedStyle().overflowX()); + } + + @Test + void overflowY_acceptsSupportedValues() { + Property property = property(OVERFLOW_Y); + Element element = NodeBuilder.div(); + + property.apply(element, new TermIdent("scroll")); + + assertEquals(Overflow.SCROLL, element.resolvedStyle().overflowY()); + } + + @Test + void overflow_singleValue_updatesBothAxes() { + Property property = property(OVERFLOW); + Element element = NodeBuilder.div(); + + property.apply(element, new TermIdent("hidden")); + + assertEquals(Overflow.HIDDEN, element.resolvedStyle().overflowX()); + assertEquals(Overflow.HIDDEN, element.resolvedStyle().overflowY()); + assertNull(element.resolvedStyle().overflow()); + } + + @Test + void overflow_twoValues_updatesXThenY() { + Property property = property(OVERFLOW); + Element element = NodeBuilder.div(); + + property.apply( + element, new TermList(Operator.SPACE, new TermIdent("auto"), new TermIdent("scroll"))); + + assertEquals(Overflow.AUTO, element.resolvedStyle().overflowX()); + assertEquals(Overflow.SCROLL, element.resolvedStyle().overflowY()); + } + + @Test + void overflow_rejectsUnsupportedValues() { + Property property = property(OVERFLOW); + Element element = NodeBuilder.div(); + + property.apply(element, new TermIdent("overlay")); + + assertNull(element.resolvedStyle().overflowX()); + assertNull(element.resolvedStyle().overflowY()); + } + + @Test + void overflow_acceptsOnlyOneOrTwoSupportedIdentifiers() { + Property property = property(OVERFLOW); + + assertTrue(property.validator().test(new TermIdent("visible"))); + assertTrue(property.validator().test(new TermIdent("hidden"))); + assertTrue(property.validator().test(new TermIdent("auto"))); + assertTrue(property.validator().test(new TermIdent("scroll"))); + assertTrue( + property + .validator() + .test(new TermList(Operator.SPACE, new TermIdent("auto"), new TermIdent("scroll")))); + assertFalse(property.validator().test(new TermIdent("overlay"))); + assertFalse( + property + .validator() + .test( + new TermList( + Operator.SPACE, + new TermIdent("auto"), + new TermIdent("scroll"), + new TermIdent("hidden")))); + assertFalse( + property + .validator() + .test(new TermList(Operator.COMMA, new TermIdent("auto"), new TermIdent("scroll")))); + } + + private Property property(String name) { + return new OverflowPropertyProvider().getProperties().stream() + .filter(property -> name.equals(property.name())) + .findFirst() + .orElseThrow(); + } +} From f5347d116a40d6c2e5a8ffc1be940e7169e4af13 Mon Sep 17 00:00:00 2001 From: SpinyOwl Date: Sat, 30 May 2026 20:47:41 +0200 Subject: [PATCH 022/121] Centralize overflow scroll semantics --- .../scrollable-overflow-containers.md | 16 +-- .../core/layout/impl/LayoutServiceImpl.java | 23 +-- .../spinygui/core/util/OverflowUtils.java | 62 +++++++++ .../spinygui/core/util/OverflowUtilsTest.java | 131 ++++++++++++++++++ 4 files changed, 203 insertions(+), 29 deletions(-) create mode 100644 spinygui.core/src/main/java/com/spinyowl/spinygui/core/util/OverflowUtils.java create mode 100644 spinygui.core/src/test/java/com/spinyowl/spinygui/core/util/OverflowUtilsTest.java diff --git a/docs/features/scrollable-overflow-containers.md b/docs/features/scrollable-overflow-containers.md index 4eca2c17..119e9c90 100644 --- a/docs/features/scrollable-overflow-containers.md +++ b/docs/features/scrollable-overflow-containers.md @@ -50,16 +50,16 @@ Support CSS `overflow`, `overflow-x`, and `overflow-y` for block and flex elemen **Purpose:** Avoid duplicating fragile checks across layout, input, rendering, and hit-testing. **Changes:** -- [ ] Add a small utility in core, for example `OverflowUtils`, with methods for `clipsX`, `clipsY`, `clipsAny`, `acceptsWheelX`, `acceptsWheelY`, `maxScrollLeft`, `maxScrollTop`, and `clampScrollOffsets`. -- [ ] Define `auto` and `scroll` as wheel-scrollable only when `scrollWidth > clientWidth` or `scrollHeight > clientHeight`. -- [ ] Define `hidden` as clipped but not wheel-scrollable. -- [ ] Define `visible` as neither clipped nor wheel-scrollable. -- [ ] Replace ad hoc scroll clamping in `LayoutServiceImpl.updateScrollAndClientSize` with the utility while preserving current zero-reset behavior when content fits. +- [x] Add a small utility in core, for example `OverflowUtils`, with methods for `clipsX`, `clipsY`, `clipsAny`, `acceptsWheelX`, `acceptsWheelY`, `maxScrollLeft`, `maxScrollTop`, and `clampScrollOffsets`. +- [x] Define `auto` and `scroll` as wheel-scrollable only when `scrollWidth > clientWidth` or `scrollHeight > clientHeight`. +- [x] Define `hidden` as clipped but not wheel-scrollable. +- [x] Define `visible` as neither clipped nor wheel-scrollable. +- [x] Replace ad hoc scroll clamping in `LayoutServiceImpl.updateScrollAndClientSize` with the utility while preserving current zero-reset behavior when content fits. **Acceptance Checks:** -- [ ] Add unit tests for max scroll and clamping: negative offsets clamp to `0`, oversized offsets clamp to `scrollSize - clientSize`, and fitting content resets offsets to `0`. -- [ ] Add tests proving `visible` does not consume wheel scroll, `hidden` clips without wheel consumption, and `auto`/`scroll` consume only when the axis overflows. -- [ ] Run `.\gradlew.bat :spinygui.core:test`. +- [x] Add unit tests for max scroll and clamping: negative offsets clamp to `0`, oversized offsets clamp to `scrollSize - clientSize`, and fitting content resets offsets to `0`. +- [x] Add tests proving `visible` does not consume wheel scroll, `hidden` clips without wheel consumption, and `auto`/`scroll` consume only when the axis overflows. +- [x] Run `.\gradlew.bat :spinygui.core:test`. **Dependencies:** Step 1. diff --git a/spinygui.core/src/main/java/com/spinyowl/spinygui/core/layout/impl/LayoutServiceImpl.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/layout/impl/LayoutServiceImpl.java index 4d315bbb..2af3e279 100644 --- a/spinygui.core/src/main/java/com/spinyowl/spinygui/core/layout/impl/LayoutServiceImpl.java +++ b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/layout/impl/LayoutServiceImpl.java @@ -4,6 +4,7 @@ import static com.spinyowl.spinygui.core.layout.impl.LayoutUtils.isPositioned; import static com.spinyowl.spinygui.core.style.types.Display.NONE; import static com.spinyowl.spinygui.core.util.NodeUtilities.visible; +import static com.spinyowl.spinygui.core.util.OverflowUtils.clampScrollOffsets; import com.spinyowl.spinygui.core.layout.ElementLayout; import com.spinyowl.spinygui.core.layout.LayoutContext; @@ -67,27 +68,7 @@ private void updateScrollAndClientSize(Element element) { element.clientWidth(clientWidth); element.clientHeight(clientHeight); - if (clientWidth >= scrollWidth) { - element.scrollLeft(0); - } else { - float scrollLeft = element.scrollLeft(); - if (scrollLeft < 0) { - element.scrollLeft(0); - } else if (scrollLeft > scrollWidth - clientWidth) { - element.scrollLeft(scrollWidth - clientWidth); - } - } - - if (clientHeight >= scrollHeight) { - element.scrollTop(0); - } else { - float scrollTop = element.scrollTop(); - if (scrollTop < 0) { - element.scrollTop(0); - } else if (scrollTop > scrollHeight - clientHeight) { - element.scrollTop(scrollHeight - clientHeight); - } - } + clampScrollOffsets(element); // update scroll and client size for all the children. element.childNodes().stream() diff --git a/spinygui.core/src/main/java/com/spinyowl/spinygui/core/util/OverflowUtils.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/util/OverflowUtils.java new file mode 100644 index 00000000..f5769e24 --- /dev/null +++ b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/util/OverflowUtils.java @@ -0,0 +1,62 @@ +package com.spinyowl.spinygui.core.util; + +import static lombok.AccessLevel.PRIVATE; + +import com.spinyowl.spinygui.core.node.Element; +import com.spinyowl.spinygui.core.style.types.Overflow; +import lombok.NoArgsConstructor; +import lombok.NonNull; + +@NoArgsConstructor(access = PRIVATE) +public final class OverflowUtils { + + public static boolean clipsX(@NonNull Element element) { + return clips(element.resolvedStyle().overflowX()); + } + + public static boolean clipsY(@NonNull Element element) { + return clips(element.resolvedStyle().overflowY()); + } + + public static boolean clipsAny(@NonNull Element element) { + return clipsX(element) || clipsY(element); + } + + public static boolean acceptsWheelX(@NonNull Element element) { + return acceptsWheel(element.resolvedStyle().overflowX(), maxScrollLeft(element)); + } + + public static boolean acceptsWheelY(@NonNull Element element) { + return acceptsWheel(element.resolvedStyle().overflowY(), maxScrollTop(element)); + } + + public static float maxScrollLeft(@NonNull Element element) { + return Math.max(0, element.scrollWidth() - element.clientWidth()); + } + + public static float maxScrollTop(@NonNull Element element) { + return Math.max(0, element.scrollHeight() - element.clientHeight()); + } + + public static void clampScrollOffsets(@NonNull Element element) { + element.scrollLeft(clamp(element.scrollLeft(), maxScrollLeft(element))); + element.scrollTop(clamp(element.scrollTop(), maxScrollTop(element))); + } + + private static boolean clips(Overflow overflow) { + return Overflow.HIDDEN.equals(overflow) + || Overflow.AUTO.equals(overflow) + || Overflow.SCROLL.equals(overflow); + } + + private static boolean acceptsWheel(Overflow overflow, float maxScroll) { + return maxScroll > 0 && (Overflow.AUTO.equals(overflow) || Overflow.SCROLL.equals(overflow)); + } + + private static float clamp(float value, float max) { + if (value < 0) { + return 0; + } + return Math.min(value, max); + } +} diff --git a/spinygui.core/src/test/java/com/spinyowl/spinygui/core/util/OverflowUtilsTest.java b/spinygui.core/src/test/java/com/spinyowl/spinygui/core/util/OverflowUtilsTest.java new file mode 100644 index 00000000..49c34329 --- /dev/null +++ b/spinygui.core/src/test/java/com/spinyowl/spinygui/core/util/OverflowUtilsTest.java @@ -0,0 +1,131 @@ +package com.spinyowl.spinygui.core.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.spinyowl.spinygui.core.node.Element; +import com.spinyowl.spinygui.core.node.NodeBuilder; +import com.spinyowl.spinygui.core.style.types.Overflow; +import org.junit.jupiter.api.Test; + +class OverflowUtilsTest { + + @Test + void maxScrollLeft_returnsPositiveOverflowRange() { + Element element = scrollableElement(300, 100, 100, 100); + + assertEquals(200, OverflowUtils.maxScrollLeft(element)); + } + + @Test + void maxScrollTop_returnsPositiveOverflowRange() { + Element element = scrollableElement(100, 350, 100, 100); + + assertEquals(250, OverflowUtils.maxScrollTop(element)); + } + + @Test + void maxScroll_returnsZeroWhenContentFits() { + Element element = scrollableElement(80, 90, 100, 100); + + assertEquals(0, OverflowUtils.maxScrollLeft(element)); + assertEquals(0, OverflowUtils.maxScrollTop(element)); + } + + @Test + void clampScrollOffsets_clampsNegativeOffsetsToZero() { + Element element = scrollableElement(300, 300, 100, 100); + element.scrollLeft(-10); + element.scrollTop(-20); + + OverflowUtils.clampScrollOffsets(element); + + assertEquals(0, element.scrollLeft()); + assertEquals(0, element.scrollTop()); + } + + @Test + void clampScrollOffsets_clampsOversizedOffsetsToMaximum() { + Element element = scrollableElement(300, 400, 100, 150); + element.scrollLeft(500); + element.scrollTop(600); + + OverflowUtils.clampScrollOffsets(element); + + assertEquals(200, element.scrollLeft()); + assertEquals(250, element.scrollTop()); + } + + @Test + void clampScrollOffsets_resetsOffsetsWhenContentFits() { + Element element = scrollableElement(80, 90, 100, 100); + element.scrollLeft(10); + element.scrollTop(20); + + OverflowUtils.clampScrollOffsets(element); + + assertEquals(0, element.scrollLeft()); + assertEquals(0, element.scrollTop()); + } + + @Test + void visibleDoesNotClipOrAcceptWheelScroll() { + Element element = scrollableElement(300, 300, 100, 100); + element.resolvedStyle().overflowX(Overflow.VISIBLE); + element.resolvedStyle().overflowY(Overflow.VISIBLE); + + assertFalse(OverflowUtils.clipsX(element)); + assertFalse(OverflowUtils.clipsY(element)); + assertFalse(OverflowUtils.clipsAny(element)); + assertFalse(OverflowUtils.acceptsWheelX(element)); + assertFalse(OverflowUtils.acceptsWheelY(element)); + } + + @Test + void hiddenClipsButDoesNotAcceptWheelScroll() { + Element element = scrollableElement(300, 300, 100, 100); + element.resolvedStyle().overflowX(Overflow.HIDDEN); + element.resolvedStyle().overflowY(Overflow.HIDDEN); + + assertTrue(OverflowUtils.clipsX(element)); + assertTrue(OverflowUtils.clipsY(element)); + assertTrue(OverflowUtils.clipsAny(element)); + assertFalse(OverflowUtils.acceptsWheelX(element)); + assertFalse(OverflowUtils.acceptsWheelY(element)); + } + + @Test + void autoAcceptsWheelOnlyWhenAxisOverflows() { + Element element = scrollableElement(300, 80, 100, 100); + element.resolvedStyle().overflowX(Overflow.AUTO); + element.resolvedStyle().overflowY(Overflow.AUTO); + + assertTrue(OverflowUtils.clipsX(element)); + assertTrue(OverflowUtils.clipsY(element)); + assertTrue(OverflowUtils.acceptsWheelX(element)); + assertFalse(OverflowUtils.acceptsWheelY(element)); + } + + @Test + void scrollAcceptsWheelOnlyWhenAxisOverflows() { + Element element = scrollableElement(90, 300, 100, 100); + element.resolvedStyle().overflowX(Overflow.SCROLL); + element.resolvedStyle().overflowY(Overflow.SCROLL); + + assertTrue(OverflowUtils.clipsX(element)); + assertTrue(OverflowUtils.clipsY(element)); + assertFalse(OverflowUtils.acceptsWheelX(element)); + assertTrue(OverflowUtils.acceptsWheelY(element)); + } + + private Element scrollableElement( + float scrollWidth, float scrollHeight, float clientWidth, float clientHeight) { + Element element = NodeBuilder.div(); + element.scrollWidth(scrollWidth); + element.scrollHeight(scrollHeight); + element.clientWidth(clientWidth); + element.clientHeight(clientHeight); + return element; + } +} From 5ffd1d9e632758b9aac341dd94d598ea91c9c47e Mon Sep 17 00:00:00 2001 From: SpinyOwl Date: Sat, 30 May 2026 23:17:06 +0200 Subject: [PATCH 023/121] Verify overflow layout metrics --- .../scrollable-overflow-containers.md | 16 +- .../core/layout/impl/LayoutServiceImpl.java | 4 +- .../core/layout/impl/OverflowLayoutTest.java | 205 ++++++++++++++++++ 3 files changed, 215 insertions(+), 10 deletions(-) create mode 100644 spinygui.core/src/test/java/com/spinyowl/spinygui/core/layout/impl/OverflowLayoutTest.java diff --git a/docs/features/scrollable-overflow-containers.md b/docs/features/scrollable-overflow-containers.md index 119e9c90..0bf245be 100644 --- a/docs/features/scrollable-overflow-containers.md +++ b/docs/features/scrollable-overflow-containers.md @@ -69,16 +69,16 @@ Support CSS `overflow`, `overflow-x`, and `overflow-y` for block and flex elemen **Purpose:** Ensure fixed or constrained containers keep their own content box while child overflow contributes to scroll ranges. **Changes:** -- [ ] Audit `BlockLayout.childrenHeight` and `FlexLayout.layout` to verify explicit `height`, `max-height`, and flex-assigned heights preserve the container content size even when children exceed it. -- [ ] Adjust scroll size calculation in `LayoutServiceImpl.updateScrollAndClientSize` so child margin boxes beyond the content box increase `scrollWidth`/`scrollHeight`, but parent `clientWidth`/`clientHeight` remain the laid-out content size. -- [ ] Use layout children or actual child nodes consistently for scroll-size calculation; document and test the choice for positioned children. Initial recommendation: keep absolute-positioned descendants out of normal scroll-size expansion to match current `affectsScrollSize`. -- [ ] Clamp offsets after every layout pass using the centralized utility. +- [x] Audit `BlockLayout.childrenHeight` and `FlexLayout.layout` to verify explicit `height`, `max-height`, and flex-assigned heights preserve the container content size even when children exceed it. +- [x] Adjust scroll size calculation in `LayoutServiceImpl.updateScrollAndClientSize` so child margin boxes beyond the content box increase `scrollWidth`/`scrollHeight`, but parent `clientWidth`/`clientHeight` remain the laid-out content size. +- [x] Use layout children or actual child nodes consistently for scroll-size calculation; document and test the choice for positioned children. Initial recommendation: keep absolute-positioned descendants out of normal scroll-size expansion to match current `affectsScrollSize`. +- [x] Clamp offsets after every layout pass using the centralized utility. **Acceptance Checks:** -- [ ] Add a block layout test where a `height: 100px; overflow-y: auto` container has a 300px-tall child: container `clientHeight` remains `100`, `scrollHeight` becomes at least `300`, and `scrollTop` clamps to `scrollHeight - clientHeight`. -- [ ] Add a flex layout test where a flex child overflows its allocated height: the flex item content box remains the Yoga-assigned size and scroll range reflects child overflow. -- [ ] Add a test that `overflow: visible` preserves the same layout box and scroll metrics but is not wheel-scrollable. -- [ ] Run `.\gradlew.bat :spinygui.core:test`. +- [x] Add a block layout test where a `height: 100px; overflow-y: auto` container has a 300px-tall child: container `clientHeight` remains `100`, `scrollHeight` becomes at least `300`, and `scrollTop` clamps to `scrollHeight - clientHeight`. +- [x] Add a flex layout test where a flex child overflows its allocated height: the flex item content box remains the Yoga-assigned size and scroll range reflects child overflow. +- [x] Add a test that `overflow: visible` preserves the same layout box and scroll metrics but is not wheel-scrollable. +- [x] Run `.\gradlew.bat :spinygui.core:test`. **Dependencies:** Steps 1 and 2. diff --git a/spinygui.core/src/main/java/com/spinyowl/spinygui/core/layout/impl/LayoutServiceImpl.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/layout/impl/LayoutServiceImpl.java index 2af3e279..d67ad44f 100644 --- a/spinygui.core/src/main/java/com/spinyowl/spinygui/core/layout/impl/LayoutServiceImpl.java +++ b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/layout/impl/LayoutServiceImpl.java @@ -56,8 +56,8 @@ private void updateScrollAndClientSize(Element element) { } Box box = element.box(); - scrollWidth = scrollWidth - box.border().left() - box.padding().left(); - scrollHeight = scrollHeight - box.border().top() - box.padding().top(); + scrollWidth = Math.max(0, scrollWidth - box.border().left() - box.padding().left()); + scrollHeight = Math.max(0, scrollHeight - box.border().top() - box.padding().top()); float clientWidth = box.content().width(); float clientHeight = box.content().height(); diff --git a/spinygui.core/src/test/java/com/spinyowl/spinygui/core/layout/impl/OverflowLayoutTest.java b/spinygui.core/src/test/java/com/spinyowl/spinygui/core/layout/impl/OverflowLayoutTest.java new file mode 100644 index 00000000..b05c1372 --- /dev/null +++ b/spinygui.core/src/test/java/com/spinyowl/spinygui/core/layout/impl/OverflowLayoutTest.java @@ -0,0 +1,205 @@ +package com.spinyowl.spinygui.core.layout.impl; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.mockito.Mockito.mock; + +import com.spinyowl.spinygui.core.event.processor.EventProcessor; +import com.spinyowl.spinygui.core.layout.ElementLayout; +import com.spinyowl.spinygui.core.layout.LayoutService; +import com.spinyowl.spinygui.core.layout.TextMeasurer; +import com.spinyowl.spinygui.core.layout.TextLayout; +import com.spinyowl.spinygui.core.node.Element; +import com.spinyowl.spinygui.core.node.Frame; +import com.spinyowl.spinygui.core.node.NodeBuilder; +import com.spinyowl.spinygui.core.style.ResolvedStyle; +import com.spinyowl.spinygui.core.style.types.Display; +import com.spinyowl.spinygui.core.style.types.Overflow; +import com.spinyowl.spinygui.core.style.types.Position; +import com.spinyowl.spinygui.core.style.types.border.BorderStyle; +import com.spinyowl.spinygui.core.style.types.flex.AlignItems; +import com.spinyowl.spinygui.core.style.types.flex.AlignSelf; +import com.spinyowl.spinygui.core.style.types.flex.FlexDirection; +import com.spinyowl.spinygui.core.style.types.flex.FlexWrap; +import com.spinyowl.spinygui.core.style.types.flex.JustifyContent; +import com.spinyowl.spinygui.core.style.types.length.Length; +import com.spinyowl.spinygui.core.style.types.length.Unit; +import com.spinyowl.spinygui.core.system.event.processor.SystemEventProcessor; +import com.spinyowl.spinygui.core.time.TimeService; +import com.spinyowl.spinygui.core.util.OverflowUtils; +import java.util.HashMap; +import org.junit.jupiter.api.Test; + +class OverflowLayoutTest { + + @Test + void layout_whenBlockChildExceedsFixedHeight_preservesClientHeightAndMeasuresScrollHeight() { + Frame frame = NodeBuilder.frame(); + frame.frameSize(500, 500); + style(frame, Display.BLOCK, 500, 500); + Element container = NodeBuilder.div(); + style(container, Display.BLOCK, 100, 100); + container.resolvedStyle().overflowY(Overflow.AUTO); + Element child = NodeBuilder.div(); + style(child, Display.BLOCK, 100, 300); + container.addChild(child); + frame.addChild(container); + + layoutService().layout(frame); + + assertEquals(100, container.box().content().height()); + assertEquals(100, container.clientHeight()); + assertEquals(300, container.scrollHeight()); + + container.scrollTop(500); + layoutService().layout(frame); + + assertEquals(200, container.scrollTop()); + } + + @Test + void layout_whenFlexItemChildExceedsAllocatedHeight_preservesItemSizeAndMeasuresOverflow() { + Frame frame = NodeBuilder.frame(); + frame.frameSize(500, 500); + style(frame, Display.FLEX, 500, 100); + frame.resolvedStyle().alignItems(AlignItems.FLEX_START); + Element item = NodeBuilder.div(); + style(item, Display.BLOCK, 100, 100); + item.resolvedStyle().overflowY(Overflow.AUTO); + Element child = NodeBuilder.div(); + style(child, Display.BLOCK, 100, 300); + item.addChild(child); + frame.addChild(item); + + layoutService().layout(frame); + + assertEquals(100, item.box().content().height()); + assertEquals(100, item.clientHeight()); + assertEquals(300, item.scrollHeight()); + assertEquals(200, OverflowUtils.maxScrollTop(item)); + } + + @Test + void layout_whenOverflowIsVisible_preservesMetricsButDoesNotAcceptWheelScroll() { + Frame frame = NodeBuilder.frame(); + frame.frameSize(500, 500); + style(frame, Display.BLOCK, 500, 500); + Element container = NodeBuilder.div(); + style(container, Display.BLOCK, 100, 100); + container.resolvedStyle().overflowY(Overflow.VISIBLE); + Element child = NodeBuilder.div(); + style(child, Display.BLOCK, 100, 300); + container.addChild(child); + frame.addChild(container); + + layoutService().layout(frame); + + assertEquals(100, container.clientHeight()); + assertEquals(300, container.scrollHeight()); + assertEquals(200, OverflowUtils.maxScrollTop(container)); + assertFalse(OverflowUtils.acceptsWheelY(container)); + } + + @Test + void layout_whenChildIsAbsolutelyPositioned_doesNotIncludeItInScrollSize() { + Frame frame = NodeBuilder.frame(); + frame.frameSize(500, 500); + style(frame, Display.BLOCK, 500, 500); + Element container = NodeBuilder.div(); + style(container, Display.BLOCK, 100, 100); + container.resolvedStyle().overflowY(Overflow.AUTO); + Element child = NodeBuilder.div(); + style(child, Display.BLOCK, 100, 300); + child.resolvedStyle().position(Position.ABSOLUTE); + container.addChild(child); + frame.addChild(container); + + layoutService().layout(frame); + + assertEquals(100, container.clientHeight()); + assertEquals(0, container.scrollHeight()); + assertEquals(0, OverflowUtils.maxScrollTop(container)); + } + + @Test + void layout_whenChildContentFits_resetsScrollOffsetAfterLayout() { + Frame frame = NodeBuilder.frame(); + frame.frameSize(500, 500); + style(frame, Display.BLOCK, 500, 500); + Element container = NodeBuilder.div(); + style(container, Display.BLOCK, 100, 100); + container.resolvedStyle().overflowY(Overflow.AUTO); + Element child = NodeBuilder.div(); + style(child, Display.BLOCK, 100, 50); + container.addChild(child); + container.scrollTop(25); + frame.addChild(container); + + layoutService().layout(frame); + + assertEquals(100, container.clientHeight()); + assertEquals(50, container.scrollHeight()); + assertEquals(0, container.scrollTop()); + assertFalse(OverflowUtils.acceptsWheelY(container)); + } + + private static LayoutService layoutService() { + var layoutMap = new HashMap(); + LayoutService layoutService = new LayoutServiceImpl(mock(TextLayout.class), layoutMap); + var blockLayout = + new BlockLayout(layoutService, new InlineFormattingContext(mock(TextMeasurer.class))); + layoutMap.put(Display.NONE, new NoneLayout()); + layoutMap.put(Display.BLOCK, blockLayout); + layoutMap.put( + Display.FLEX, + new FlexLayout( + mock(SystemEventProcessor.class), + mock(EventProcessor.class), + mock(TimeService.class), + blockLayout, + layoutService)); + return layoutService; + } + + private static void style(Element element, Display display, float width, float height) { + ResolvedStyle style = element.resolvedStyle(); + style.display(display); + style.position(Position.STATIC); + style.width(Length.pixel(width)); + style.height(Length.pixel(height)); + style.minWidth(null); + style.maxWidth(null); + style.minHeight(null); + style.maxHeight(null); + style.top(Unit.AUTO); + style.right(Unit.AUTO); + style.bottom(Unit.AUTO); + style.left(Unit.AUTO); + style.paddingTop(Length.ZERO); + style.paddingRight(Length.ZERO); + style.paddingBottom(Length.ZERO); + style.paddingLeft(Length.ZERO); + style.marginTop(Length.ZERO); + style.marginRight(Length.ZERO); + style.marginBottom(Length.ZERO); + style.marginLeft(Length.ZERO); + style.borderTopWidth(Length.pixel(0)); + style.borderRightWidth(Length.pixel(0)); + style.borderBottomWidth(Length.pixel(0)); + style.borderLeftWidth(Length.pixel(0)); + style.borderTopStyle(BorderStyle.NONE); + style.borderRightStyle(BorderStyle.NONE); + style.borderBottomStyle(BorderStyle.NONE); + style.borderLeftStyle(BorderStyle.NONE); + style.overflowX(Overflow.VISIBLE); + style.overflowY(Overflow.VISIBLE); + style.flexBasis(Unit.AUTO); + style.flexDirection(FlexDirection.ROW); + style.flexWrap(FlexWrap.NOWRAP); + style.flexGrow(0); + style.flexShrink(0); + style.justifyContent(JustifyContent.FLEX_START); + style.alignItems(AlignItems.FLEX_START); + style.alignSelf(AlignSelf.AUTO); + } +} From 221be17648baec430762544e10085e7fe5ef12c9 Mon Sep 17 00:00:00 2001 From: SpinyOwl Date: Sat, 30 May 2026 23:43:51 +0200 Subject: [PATCH 024/121] Implement scroll event chaining --- .../scrollable-overflow-containers.md | 22 +- .../listener/SystemScrollEventListener.java | 118 +++++++- .../SystemScrollEventListenerTest.java | 258 ++++++++++++++++-- 3 files changed, 352 insertions(+), 46 deletions(-) diff --git a/docs/features/scrollable-overflow-containers.md b/docs/features/scrollable-overflow-containers.md index 0bf245be..097f52cd 100644 --- a/docs/features/scrollable-overflow-containers.md +++ b/docs/features/scrollable-overflow-containers.md @@ -88,19 +88,19 @@ Support CSS `overflow`, `overflow-x`, and `overflow-y` for block and flex elemen **Purpose:** Update only the nearest eligible scroll container, and bubble/chains scroll to ancestors only when the current container cannot consume more in that direction. **Changes:** -- [ ] Refactor `SystemScrollEventListener` to build a target chain from hovered elements, deepest first. -- [ ] Include `frame.getFocusedElement()` as a fallback chain only when no hovered candidate can consume the event. -- [ ] For each axis, find the deepest candidate whose overflow mode allows wheel scrolling and whose offset can move in the requested direction. -- [ ] Apply delta per axis and clamp through `OverflowUtils`; if one axis is consumed by a child, do not also scroll an ancestor on that axis. -- [ ] Push `ScrollEvent` only for elements whose scroll offset actually changed. -- [ ] Keep existing event offsets in the generated `ScrollEvent` so listeners can see the original wheel delta. +- [x] Refactor `SystemScrollEventListener` to build a target chain from hovered elements, deepest first. +- [x] Include `frame.getFocusedElement()` as a fallback chain only when no hovered candidate can consume the event. +- [x] For each axis, find the deepest candidate whose overflow mode allows wheel scrolling and whose offset can move in the requested direction. +- [x] Apply delta per axis and clamp through `OverflowUtils`; if one axis is consumed by a child, do not also scroll an ancestor on that axis. +- [x] Push `ScrollEvent` only for elements whose scroll offset actually changed. +- [x] Keep existing event offsets in the generated `ScrollEvent` so listeners can see the original wheel delta. **Acceptance Checks:** -- [ ] Add listener tests where an inner scroll container consumes vertical scroll and the outer ancestor remains unchanged. -- [ ] Add a test where an inner container at max scroll passes further scroll to the outer container. -- [ ] Add a test where `overflow-y: hidden` does not consume wheel input and an eligible ancestor can scroll. -- [ ] Add a horizontal scroll test using `offsetX`. -- [ ] Run `.\gradlew.bat :spinygui.core:test`. +- [x] Add listener tests where an inner scroll container consumes vertical scroll and the outer ancestor remains unchanged. +- [x] Add a test where an inner container at max scroll passes further scroll to the outer container. +- [x] Add a test where `overflow-y: hidden` does not consume wheel input and an eligible ancestor can scroll. +- [x] Add a horizontal scroll test using `offsetX`. +- [x] Run `.\gradlew.bat :spinygui.core:test`. **Dependencies:** Steps 1 through 3. diff --git a/spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemScrollEventListener.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemScrollEventListener.java index c7f5eebb..c54bb441 100644 --- a/spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemScrollEventListener.java +++ b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/system/event/listener/SystemScrollEventListener.java @@ -3,10 +3,16 @@ import com.spinyowl.spinygui.core.event.ScrollEvent; import com.spinyowl.spinygui.core.event.processor.EventProcessor; import com.spinyowl.spinygui.core.input.MouseService; +import com.spinyowl.spinygui.core.node.Element; import com.spinyowl.spinygui.core.node.Frame; import com.spinyowl.spinygui.core.system.event.SystemScrollEvent; import com.spinyowl.spinygui.core.time.TimeService; import com.spinyowl.spinygui.core.util.NodeUtilities; +import com.spinyowl.spinygui.core.util.OverflowUtils; +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; import lombok.Builder; import lombok.EqualsAndHashCode; import lombok.NonNull; @@ -35,23 +41,29 @@ public SystemScrollEventListener( @Override public void process(@NonNull SystemScrollEvent event, @NonNull Frame frame) { Vector2fc current = mouseService.getCursorPositions(frame).current(); - var currentTargetElements = NodeUtilities.getTargetElementList(frame, current); - float multiplier = 50; - for (var target : currentTargetElements) { - // TODO: - // 1. If target prevents scroll - skip scrolling. - // 2. Instead of direct updating of scrollTop and scrollLeft as another option we can start - // scroll animation. - - float scrollTop = target.scrollTop() - event.offsetY() * multiplier; - if (target.scrollHeight() > target.clientHeight()) { - target.scrollTop(scrollTop); - } + var hoveredChain = deepestFirst(NodeUtilities.getTargetElementList(frame, current)); + + float horizontalDelta = -event.offsetX() * 50; + float verticalDelta = -event.offsetY() * 50; + Set changedElements = new LinkedHashSet<>(); - float scrollLeft = target.scrollLeft() - event.offsetX() * multiplier; - if (target.scrollWidth() > target.box().content().width()) { - target.scrollLeft(scrollLeft); + boolean horizontalConsumed = consumeHorizontal(hoveredChain, horizontalDelta, changedElements); + boolean verticalConsumed = consumeVertical(hoveredChain, verticalDelta, changedElements); + + if (!horizontalConsumed || !verticalConsumed) { + Element focusedElement = frame.getFocusedElement(); + if (focusedElement != null && !hoveredChain.contains(focusedElement)) { + var focusedChain = focusedChain(focusedElement); + if (!horizontalConsumed) { + consumeHorizontal(focusedChain, horizontalDelta, changedElements); + } + if (!verticalConsumed) { + consumeVertical(focusedChain, verticalDelta, changedElements); + } } + } + + for (Element target : changedElements) { eventProcessor.push( ScrollEvent.builder() .source(frame) @@ -62,4 +74,80 @@ public void process(@NonNull SystemScrollEvent event, @NonNull Frame frame) { .build()); } } + + private boolean consumeHorizontal( + List candidates, float delta, Set changedElements) { + if (delta == 0) { + return true; + } + for (Element target : candidates) { + if (canConsumeHorizontal(target, delta)) { + float previous = target.scrollLeft(); + target.scrollLeft(previous + delta); + OverflowUtils.clampScrollOffsets(target); + if (target.scrollLeft() != previous) { + changedElements.add(target); + return true; + } + } + } + return false; + } + + private boolean consumeVertical( + List candidates, float delta, Set changedElements) { + if (delta == 0) { + return true; + } + for (Element target : candidates) { + if (canConsumeVertical(target, delta)) { + float previous = target.scrollTop(); + target.scrollTop(previous + delta); + OverflowUtils.clampScrollOffsets(target); + if (target.scrollTop() != previous) { + changedElements.add(target); + return true; + } + } + } + return false; + } + + private boolean canConsumeHorizontal(Element target, float delta) { + return OverflowUtils.acceptsWheelX(target) + && (delta < 0 && target.scrollLeft() > 0 + || delta > 0 && target.scrollLeft() < OverflowUtils.maxScrollLeft(target)); + } + + private boolean canConsumeVertical(Element target, float delta) { + return OverflowUtils.acceptsWheelY(target) + && (delta < 0 && target.scrollTop() > 0 + || delta > 0 && target.scrollTop() < OverflowUtils.maxScrollTop(target)); + } + + private List deepestFirst(List elements) { + var deepestFirst = new ArrayList(elements); + deepestFirst.sort((left, right) -> Integer.compare(depth(right), depth(left))); + return deepestFirst; + } + + private List focusedChain(Element focusedElement) { + var chain = new ArrayList(); + Element current = focusedElement; + while (current != null) { + chain.add(current); + current = current.parent(); + } + return chain; + } + + private int depth(Element element) { + int depth = 0; + Element current = element.parent(); + while (current != null) { + depth++; + current = current.parent(); + } + return depth; + } } diff --git a/spinygui.core/src/test/java/com/spinyowl/spinygui/core/system/event/listener/SystemScrollEventListenerTest.java b/spinygui.core/src/test/java/com/spinyowl/spinygui/core/system/event/listener/SystemScrollEventListenerTest.java index 28c17228..bba62d78 100644 --- a/spinygui.core/src/test/java/com/spinyowl/spinygui/core/system/event/listener/SystemScrollEventListenerTest.java +++ b/spinygui.core/src/test/java/com/spinyowl/spinygui/core/system/event/listener/SystemScrollEventListenerTest.java @@ -1,18 +1,23 @@ package com.spinyowl.spinygui.core.system.event.listener; import static com.spinyowl.spinygui.core.node.NodeBuilder.frame; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import com.spinyowl.spinygui.core.event.ScrollEvent; import com.spinyowl.spinygui.core.event.processor.EventProcessor; import com.spinyowl.spinygui.core.input.MouseService; import com.spinyowl.spinygui.core.input.MouseService.CursorPositions; +import com.spinyowl.spinygui.core.node.Element; import com.spinyowl.spinygui.core.node.Frame; +import com.spinyowl.spinygui.core.node.NodeBuilder; import com.spinyowl.spinygui.core.system.event.SystemScrollEvent; import com.spinyowl.spinygui.core.time.TimeService; +import com.spinyowl.spinygui.core.style.types.Overflow; import org.joml.Vector2f; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; @@ -43,15 +48,84 @@ void setUp() { } @Test - void process_generatesScrollEvent() { - Frame frame = new Frame(); - frame.box().contentSize(100, 100); + void process_whenInnerCanScrollVertically_scrollsInnerOnly() { + Frame frame = frame(); + Element outer = scrollContainer(0, 0, 200, 200, 200, 500); + Element inner = scrollContainer(0, 0, 100, 100, 100, 300); + frame.addChild(outer); + outer.addChild(inner); + frame.box().contentSize(500, 500); + + double timestamp = 1D; + cursorAt(frame, 10, 10); + when(timeService.currentTime()).thenReturn(timestamp); + + SystemScrollEvent event = createEvent(frame); + + ScrollEvent expectedEvent = + ScrollEvent.builder() + .source(frame) + .target(inner) + .timestamp(timestamp) + .offsetX(OFFSET_X) + .offsetY(OFFSET_Y) + .build(); + doNothing().when(eventProcessor).push(expectedEvent); + + listener.process(event, frame); + + assertEquals(50, inner.scrollTop()); + assertEquals(0, outer.scrollTop()); + verify(eventProcessor).push(expectedEvent); + verifyNoMoreInteractions(eventProcessor); + } + + @Test + void process_whenInnerAtScrollLimit_chainsVerticalScrollToOuter() { + Frame frame = frame(); + Element outer = scrollContainer(0, 0, 200, 200, 200, 500); + Element inner = scrollContainer(0, 0, 100, 100, 100, 300); + inner.scrollTop(200); + frame.addChild(outer); + outer.addChild(inner); + frame.box().contentSize(500, 500); + + double timestamp = 1D; + cursorAt(frame, 10, 10); + when(timeService.currentTime()).thenReturn(timestamp); + + SystemScrollEvent event = createEvent(frame); + + ScrollEvent expectedEvent = + ScrollEvent.builder() + .source(frame) + .target(outer) + .timestamp(timestamp) + .offsetX(OFFSET_X) + .offsetY(OFFSET_Y) + .build(); + doNothing().when(eventProcessor).push(expectedEvent); - Vector2f current = new Vector2f(10, 10); - CursorPositions cursorPositions = new CursorPositions(current, current); - when(mouseService.getCursorPositions(frame)).thenReturn(cursorPositions); + listener.process(event, frame); + + assertEquals(200, inner.scrollTop()); + assertEquals(50, outer.scrollTop()); + verify(eventProcessor).push(expectedEvent); + verifyNoMoreInteractions(eventProcessor); + } + + @Test + void process_whenInnerOverflowHidden_doesNotConsumeAndAncestorScrolls() { + Frame frame = frame(); + Element outer = scrollContainer(0, 0, 200, 200, 200, 500); + Element inner = scrollContainer(0, 0, 100, 100, 100, 300); + inner.resolvedStyle().overflowY(Overflow.HIDDEN); + frame.addChild(outer); + outer.addChild(inner); + frame.box().contentSize(500, 500); double timestamp = 1D; + cursorAt(frame, 10, 10); when(timeService.currentTime()).thenReturn(timestamp); SystemScrollEvent event = createEvent(frame); @@ -59,40 +133,160 @@ void process_generatesScrollEvent() { ScrollEvent expectedEvent = ScrollEvent.builder() .source(frame) - .target(frame) + .target(outer) .timestamp(timestamp) .offsetX(OFFSET_X) .offsetY(OFFSET_Y) .build(); doNothing().when(eventProcessor).push(expectedEvent); - // Act listener.process(event, frame); - // Verify - verify(mouseService).getCursorPositions(frame); - verify(timeService).currentTime(); + assertEquals(0, inner.scrollTop()); + assertEquals(50, outer.scrollTop()); + verify(eventProcessor).push(expectedEvent); + verifyNoMoreInteractions(eventProcessor); + } + + @Test + void process_whenHorizontalAxisCanScroll_scrollsHorizontally() { + Frame frame = frame(); + Element target = scrollContainer(0, 0, 100, 100, 300, 100); + frame.addChild(target); + frame.box().contentSize(500, 500); + + double timestamp = 1D; + cursorAt(frame, 10, 10); + when(timeService.currentTime()).thenReturn(timestamp); + + SystemScrollEvent event = + SystemScrollEvent.builder().frame(frame).offsetX(-1).offsetY(0).build(); + + ScrollEvent expectedEvent = + ScrollEvent.builder() + .source(frame) + .target(target) + .timestamp(timestamp) + .offsetX(-1) + .offsetY(0) + .build(); + doNothing().when(eventProcessor).push(expectedEvent); + + listener.process(event, frame); + + assertEquals(0, target.scrollTop()); + assertEquals(50, target.scrollLeft()); verify(eventProcessor).push(expectedEvent); + verifyNoMoreInteractions(eventProcessor); } @Test - void process_doNotGenerateScrollEvent() { - Frame frame = new Frame(); + void process_whenHorizontalReverseScrollCanMove_scrollsHorizontally() { + Frame frame = frame(); + Element target = scrollContainer(0, 0, 100, 100, 300, 100); + target.scrollLeft(50); + frame.addChild(target); + frame.box().contentSize(500, 500); + + double timestamp = 1D; + cursorAt(frame, 10, 10); + when(timeService.currentTime()).thenReturn(timestamp); + + SystemScrollEvent event = + SystemScrollEvent.builder().frame(frame).offsetX(1).offsetY(0).build(); + + ScrollEvent expectedEvent = + ScrollEvent.builder() + .source(frame) + .target(target) + .timestamp(timestamp) + .offsetX(1) + .offsetY(0) + .build(); + doNothing().when(eventProcessor).push(expectedEvent); + + listener.process(event, frame); + + assertEquals(0, target.scrollLeft()); + verify(eventProcessor).push(expectedEvent); + verifyNoMoreInteractions(eventProcessor); + } + + @Test + void process_whenNoHoveredElementCanConsumeScroll_usesFocusedElementFallback() { + Frame frame = frame(); + Element hovered = element(0, 0, 100, 100); + Element focused = scrollContainer(200, 0, 100, 100, 100, 300); + focused.focused(true); + frame.addChild(hovered); + frame.addChild(focused); frame.box().contentSize(100, 100); - Vector2f current = new Vector2f(-10, -10); - CursorPositions cursorPositions = new CursorPositions(current, current); - when(mouseService.getCursorPositions(frame)).thenReturn(cursorPositions); + double timestamp = 1D; + cursorAt(frame, 10, 10); + when(timeService.currentTime()).thenReturn(timestamp); SystemScrollEvent event = createEvent(frame); - // Act + ScrollEvent expectedEvent = + ScrollEvent.builder() + .source(frame) + .target(focused) + .timestamp(timestamp) + .offsetX(OFFSET_X) + .offsetY(OFFSET_Y) + .build(); + doNothing().when(eventProcessor).push(expectedEvent); + listener.process(event, frame); - // Verify - verify(mouseService).getCursorPositions(frame); - verifyNoInteractions(timeService); + assertEquals(50, focused.scrollTop()); + verify(eventProcessor).push(expectedEvent); + verifyNoMoreInteractions(eventProcessor); + } + + @Test + void process_whenCursorOutsideFrame_doNotGenerateScrollEvent() { + Frame frame = frame(); + frame.box().contentSize(100, 100); + + cursorAt(frame, -10, -10); + + listener.process(createEvent(frame), frame); + verifyNoInteractions(eventProcessor); + verifyNoInteractions(timeService); + } + + @Test + void process_whenCursorOutsideFrameAndFocusedElementCanScroll_usesFocusedElementFallback() { + Frame frame = frame(); + Element focused = scrollContainer(200, 0, 100, 100, 100, 300); + focused.focused(true); + frame.addChild(focused); + frame.box().contentSize(100, 100); + + double timestamp = 1D; + cursorAt(frame, -10, -10); + when(timeService.currentTime()).thenReturn(timestamp); + + SystemScrollEvent event = createEvent(frame); + + ScrollEvent expectedEvent = + ScrollEvent.builder() + .source(frame) + .target(focused) + .timestamp(timestamp) + .offsetX(OFFSET_X) + .offsetY(OFFSET_Y) + .build(); + doNothing().when(eventProcessor).push(expectedEvent); + + listener.process(event, frame); + + assertEquals(50, focused.scrollTop()); + verify(eventProcessor).push(expectedEvent); + verifyNoMoreInteractions(eventProcessor); } @Test @@ -110,4 +304,28 @@ void process_throwsNPE_ifEventIsNull() { private SystemScrollEvent createEvent(Frame frame) { return SystemScrollEvent.builder().frame(frame).offsetX(OFFSET_X).offsetY(OFFSET_Y).build(); } + + private void cursorAt(Frame frame, float x, float y) { + Vector2f current = new Vector2f(x, y); + when(mouseService.getCursorPositions(frame)).thenReturn(new CursorPositions(current, current)); + } + + private Element scrollContainer( + float x, float y, float width, float height, float scrollWidth, float scrollHeight) { + Element element = element(x, y, width, height); + element.clientWidth(width); + element.clientHeight(height); + element.scrollWidth(scrollWidth); + element.scrollHeight(scrollHeight); + element.resolvedStyle().overflowX(Overflow.AUTO); + element.resolvedStyle().overflowY(Overflow.AUTO); + return element; + } + + private Element element(float x, float y, float width, float height) { + Element element = NodeBuilder.div(); + element.box().contentPosition(x, y); + element.box().contentSize(width, height); + return element; + } } From 1a1f9814c5a438ec5496e4f3b3a0eb25d33a1d0d Mon Sep 17 00:00:00 2001 From: SpinyOwl Date: Sat, 30 May 2026 23:51:32 +0200 Subject: [PATCH 025/121] Add overflow-aware hit testing --- .../scrollable-overflow-containers.md | 16 ++-- .../spinygui/core/util/NodeUtilities.java | 33 ++++++- .../util/NodeUtilitiesOverflowHitTest.java | 89 +++++++++++++++++++ 3 files changed, 127 insertions(+), 11 deletions(-) create mode 100644 spinygui.core/src/test/java/com/spinyowl/spinygui/core/util/NodeUtilitiesOverflowHitTest.java diff --git a/docs/features/scrollable-overflow-containers.md b/docs/features/scrollable-overflow-containers.md index 097f52cd..326724c5 100644 --- a/docs/features/scrollable-overflow-containers.md +++ b/docs/features/scrollable-overflow-containers.md @@ -110,16 +110,16 @@ Support CSS `overflow`, `overflow-x`, and `overflow-y` for block and flex elemen **Purpose:** Prevent events from reaching content clipped outside a scroll container and make scrolled child positions hit-test correctly. **Changes:** -- [ ] Add a helper that checks whether a point is inside every clipping ancestor's content box before accepting an element as a target. -- [ ] Update `NodeUtilities.getTargetElement`, `getTargetElementList`, and recursive helpers to apply the clipping-ancestor check before descending into children. -- [ ] Preserve z-index ordering and `pointer-events` behavior. -- [ ] Use `absolutePosition()` for child position checks so existing offset subtraction applies scroll offsets during hit-testing. +- [x] Add a helper that checks whether a point is inside every clipping ancestor's content box before accepting an element as a target. +- [x] Update `NodeUtilities.getTargetElement`, `getTargetElementList`, and recursive helpers to apply the clipping-ancestor check before descending into children. +- [x] Preserve z-index ordering and `pointer-events` behavior. +- [x] Use `absolutePosition()` for child position checks so existing offset subtraction applies scroll offsets during hit-testing. **Acceptance Checks:** -- [ ] Add a hit-test test where a child visually outside an `overflow: hidden` container is not returned. -- [ ] Add a hit-test test where a child scrolled into view by `scrollTop` is returned at its visible viewport position. -- [ ] Add a nested scroll-container test where the inner clipped viewport limits its descendants independently from the outer container. -- [ ] Run `.\gradlew.bat :spinygui.core:test`. +- [x] Add a hit-test test where a child visually outside an `overflow: hidden` container is not returned. +- [x] Add a hit-test test where a child scrolled into view by `scrollTop` is returned at its visible viewport position. +- [x] Add a nested scroll-container test where the inner clipped viewport limits its descendants independently from the outer container. +- [x] Run `.\gradlew.bat :spinygui.core:test`. **Dependencies:** Steps 1 through 3. diff --git a/spinygui.core/src/main/java/com/spinyowl/spinygui/core/util/NodeUtilities.java b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/util/NodeUtilities.java index 373032e3..12120cb8 100644 --- a/spinygui.core/src/main/java/com/spinyowl/spinygui/core/util/NodeUtilities.java +++ b/spinygui.core/src/main/java/com/spinyowl/spinygui/core/util/NodeUtilities.java @@ -132,7 +132,7 @@ public static Element getTargetElement( final Element initialTarget, final boolean searchOnlyClickable) { Element retarget = initialTarget; - if (visible(element) && element.intersection().intersects(element, vector)) { + if (visibleAndIntersects(element, vector)) { if (!searchOnlyClickable || clickable(element)) { retarget = element; } @@ -140,7 +140,7 @@ public static Element getTargetElement( childElements.sort(Comparator.comparing(comparator)); for (Element child : childElements) { - retarget = getTargetElement(child, vector, retarget); + retarget = getTargetElement(child, vector, retarget, searchOnlyClickable); } } return retarget; @@ -171,13 +171,40 @@ public static List getTargetElementList(final Element element, final Ve */ public static List fillTargetElementList( final Vector2fc vector, final Element element, final List targetList) { - if (visible(element) && element.intersection().intersects(element, vector)) { + if (visibleAndIntersects(element, vector)) { targetList.add(element); element.children().forEach(child -> fillTargetElementList(vector, child, targetList)); } return targetList; } + private static boolean visibleAndIntersects(final Element element, final Vector2fc vector) { + return visible(element) + && element.intersection().intersects(element, vector) + && insideClippingAncestors(element, vector); + } + + private static boolean insideClippingAncestors(final Node node, final Vector2fc vector) { + for (Element parent = node.parent(); parent != null; parent = parent.parent()) { + if (OverflowUtils.clipsAny(parent) && !insideContentBox(parent, vector)) { + return false; + } + } + return true; + } + + private static boolean insideContentBox(final Element element, final Vector2fc vector) { + var absolutePosition = element.absolutePosition(); + var box = element.box(); + var contentX = absolutePosition.x() + box.border().left() + box.padding().left(); + var contentY = absolutePosition.y() + box.border().top() + box.padding().top(); + var contentSize = box.contentSize(); + return vector.x() >= contentX + && vector.x() < contentX + contentSize.x() + && vector.y() >= contentY + && vector.y() < contentY + contentSize.y(); + } + public static boolean visible(Node node) { if (node instanceof Text) return true; if (node instanceof Element element) { diff --git a/spinygui.core/src/test/java/com/spinyowl/spinygui/core/util/NodeUtilitiesOverflowHitTest.java b/spinygui.core/src/test/java/com/spinyowl/spinygui/core/util/NodeUtilitiesOverflowHitTest.java new file mode 100644 index 00000000..dbf9251c --- /dev/null +++ b/spinygui.core/src/test/java/com/spinyowl/spinygui/core/util/NodeUtilitiesOverflowHitTest.java @@ -0,0 +1,89 @@ +package com.spinyowl.spinygui.core.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.spinyowl.spinygui.core.node.Element; +import com.spinyowl.spinygui.core.node.Frame; +import com.spinyowl.spinygui.core.node.NodeBuilder; +import com.spinyowl.spinygui.core.style.types.Overflow; +import org.joml.Vector2f; +import org.junit.jupiter.api.Test; + +class NodeUtilitiesOverflowHitTest { + + @Test + void getTargetElement_excludesChildOutsideHiddenOverflowContentBox() { + Frame frame = frame(500, 500); + Element container = clippedElement(0, 0, 100, 100); + container.box().padding().right(50); + Element child = element(110, 10, 20, 20); + frame.addChild(container); + container.addChild(child); + child.offsetParent(container); + + Vector2f pointInsideChildButOutsideContent = new Vector2f(115, 15); + + assertEquals(container, NodeUtilities.getTargetElement(frame, pointInsideChildButOutsideContent)); + assertFalse( + NodeUtilities.getTargetElementList(frame, pointInsideChildButOutsideContent).contains(child)); + } + + @Test + void getTargetElement_returnsChildScrolledIntoVisibleContentBox() { + Frame frame = frame(500, 500); + Element container = clippedElement(0, 0, 100, 100); + container.scrollTop(100); + Element child = element(10, 150, 30, 30); + frame.addChild(container); + container.addChild(child); + child.offsetParent(container); + + Vector2f visiblePoint = new Vector2f(15, 55); + + assertEquals(child, NodeUtilities.getTargetElement(frame, visiblePoint)); + assertTrue(NodeUtilities.getTargetElementList(frame, visiblePoint).contains(child)); + } + + @Test + void getTargetElement_excludesChildOutsideInnerNestedScrollContentBox() { + Frame frame = frame(500, 500); + Element outer = clippedElement(0, 0, 200, 200); + Element inner = clippedElement(10, 10, 80, 80); + inner.box().padding().right(40); + Element child = element(95, 20, 20, 20); + frame.addChild(outer); + outer.addChild(inner); + inner.addChild(child); + inner.offsetParent(outer); + child.offsetParent(inner); + + Vector2f pointInsideChildButOutsideInnerContent = new Vector2f(110, 30); + + assertEquals(inner, NodeUtilities.getTargetElement(frame, pointInsideChildButOutsideInnerContent)); + assertFalse( + NodeUtilities.getTargetElementList(frame, pointInsideChildButOutsideInnerContent) + .contains(child)); + } + + private Frame frame(float width, float height) { + Frame frame = NodeBuilder.frame(); + frame.box().contentSize(width, height); + return frame; + } + + private Element clippedElement(float x, float y, float width, float height) { + Element element = element(x, y, width, height); + element.resolvedStyle().overflowX(Overflow.HIDDEN); + element.resolvedStyle().overflowY(Overflow.HIDDEN); + return element; + } + + private Element element(float x, float y, float width, float height) { + Element element = NodeBuilder.div(); + element.box().contentPosition(x, y); + element.box().contentSize(width, height); + return element; + } +} From 06e5f46cfc5d0d0a3fb5892ad75265b4d1be4c1c Mon Sep 17 00:00:00 2001 From: SpinyOwl Date: Sat, 30 May 2026 23:58:15 +0200 Subject: [PATCH 026/121] Apply overflow clipping in NanoVG renderer --- .../scrollable-overflow-containers.md | 18 +-- .../lwjgl/nanovg/util/NvgClipStack.java | 95 ++++++++++++++++ .../lwjgl/nanovg/util/NvgRenderUtils.java | 42 +------ .../lwjgl/nanovg/util/NvgClipStackTest.java | 103 ++++++++++++++++++ 4 files changed, 212 insertions(+), 46 deletions(-) create mode 100644 spinygui.core.backend.lwjgl.nanovg/src/main/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/util/NvgClipStack.java create mode 100644 spinygui.core.backend.lwjgl.nanovg/src/test/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/util/NvgClipStackTest.java diff --git a/docs/features/scrollable-overflow-containers.md b/docs/features/scrollable-overflow-containers.md index 326724c5..bdc256d0 100644 --- a/docs/features/scrollable-overflow-containers.md +++ b/docs/features/scrollable-overflow-containers.md @@ -129,17 +129,17 @@ Support CSS `overflow`, `overflow-x`, and `overflow-y` for block and flex elemen **Purpose:** Render scroll-container contents only inside their content box while allowing `overflow: visible` descendants to paint outside ancestors. **Changes:** -- [ ] Split NanoVG clipping into an injectable or testable clip helper, for example `NvgClipStack`, so tests can assert scissor/intersect/reset calls without requiring a live OpenGL context. -- [ ] Change `NvgRenderUtils.createScissor` or its replacement so it intersects only ancestors whose overflow clips on at least one axis. -- [ ] Clip children of scroll containers to the element content box; do not clip the element's own background/border to its own content box. -- [ ] Preserve clipping from multiple nested scroll containers by intersecting from root to leaf. -- [ ] Ensure text and inline-fragment render paths use the same clipping helper as element rendering. +- [x] Split NanoVG clipping into an injectable or testable clip helper, for example `NvgClipStack`, so tests can assert scissor/intersect/reset calls without requiring a live OpenGL context. +- [x] Change `NvgRenderUtils.createScissor` or its replacement so it intersects only ancestors whose overflow clips on at least one axis. +- [x] Clip children of scroll containers to the element content box; do not clip the element's own background/border to its own content box. +- [x] Preserve clipping from multiple nested scroll containers by intersecting from root to leaf. +- [x] Ensure text and inline-fragment render paths use the same clipping helper as element rendering. **Acceptance Checks:** -- [ ] Add backend tests with a fake clip sink proving `overflow: visible` ancestors do not emit clip calls. -- [ ] Add backend tests proving `overflow: hidden`, `auto`, and `scroll` ancestors clip to content-box coordinates. -- [ ] Add a nested clipping test proving root-to-leaf intersection order. -- [ ] Run `.\gradlew.bat :spinygui.core.backend.lwjgl.nanovg:test`. +- [x] Add backend tests with a fake clip sink proving `overflow: visible` ancestors do not emit clip calls. +- [x] Add backend tests proving `overflow: hidden`, `auto`, and `scroll` ancestors clip to content-box coordinates. +- [x] Add a nested clipping test proving root-to-leaf intersection order. +- [x] Run `.\gradlew.bat :spinygui.core.backend.lwjgl.nanovg:test`. **Dependencies:** Steps 1, 2, and 5. diff --git a/spinygui.core.backend.lwjgl.nanovg/src/main/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/util/NvgClipStack.java b/spinygui.core.backend.lwjgl.nanovg/src/main/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/util/NvgClipStack.java new file mode 100644 index 00000000..88d7e0a8 --- /dev/null +++ b/spinygui.core.backend.lwjgl.nanovg/src/main/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/util/NvgClipStack.java @@ -0,0 +1,95 @@ +package com.spinyowl.spinygui.core.backend.renderer.lwjgl.nanovg.util; + +import static org.lwjgl.nanovg.NanoVG.nvgIntersectScissor; +import static org.lwjgl.nanovg.NanoVG.nvgResetScissor; +import static org.lwjgl.nanovg.NanoVG.nvgScissor; + +import com.spinyowl.spinygui.core.node.Element; +import com.spinyowl.spinygui.core.node.Node; +import com.spinyowl.spinygui.core.util.OverflowUtils; +import java.util.Iterator; +import java.util.LinkedList; +import lombok.NonNull; + +public class NvgClipStack { + + private final ClipSink sink; + + public NvgClipStack(@NonNull ClipSink sink) { + this.sink = sink; + } + + public void create(long context, Node node) { + if (node != null) { + createByParent(context, node.offsetParent()); + } + } + + public void createByParent(long context, Node parent) { + if (parent == null) { + return; + } + + var parents = new LinkedList(); + Node current = parent; + while (current != null) { + if (current instanceof Element element && OverflowUtils.clipsAny(element)) { + parents.add(element); + } + current = current.offsetParent(); + } + + if (parents.isEmpty()) { + return; + } + + Iterator descendingIterator = parents.descendingIterator(); + clip(context, descendingIterator.next(), true); + while (descendingIterator.hasNext()) { + clip(context, descendingIterator.next(), false); + } + } + + public void reset(long context) { + sink.reset(context); + } + + private void clip(long context, Element element, boolean first) { + var absolutePosition = element.absolutePosition(); + var box = element.box(); + float x = absolutePosition.x() + box.border().left() + box.padding().left(); + float y = absolutePosition.y() + box.border().top() + box.padding().top(); + var contentSize = box.contentSize(); + if (first) { + sink.scissor(context, x, y, contentSize.x(), contentSize.y()); + } else { + sink.intersectScissor(context, x, y, contentSize.x(), contentSize.y()); + } + } + + public interface ClipSink { + void scissor(long context, float x, float y, float width, float height); + + void intersectScissor(long context, float x, float y, float width, float height); + + void reset(long context); + } + + public static final class NanoVgClipSink implements ClipSink { + + @Override + public void scissor(long context, float x, float y, float width, float height) { + nvgScissor(context, x, y, width, height); + } + + @Override + public void intersectScissor(long context, float x, float y, float width, float height) { + nvgIntersectScissor(context, x, y, width, height); + } + + @Override + public void reset(long context) { + nvgResetScissor(context); + } + } +} diff --git a/spinygui.core.backend.lwjgl.nanovg/src/main/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/util/NvgRenderUtils.java b/spinygui.core.backend.lwjgl.nanovg/src/main/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/util/NvgRenderUtils.java index 494e16aa..3a5b5140 100644 --- a/spinygui.core.backend.lwjgl.nanovg/src/main/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/util/NvgRenderUtils.java +++ b/spinygui.core.backend.lwjgl.nanovg/src/main/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/util/NvgRenderUtils.java @@ -19,12 +19,9 @@ import static org.lwjgl.nanovg.NanoVG.nvgBoxGradient; import static org.lwjgl.nanovg.NanoVG.nvgFill; import static org.lwjgl.nanovg.NanoVG.nvgFillPaint; -import static org.lwjgl.nanovg.NanoVG.nvgIntersectScissor; import static org.lwjgl.nanovg.NanoVG.nvgPathWinding; -import static org.lwjgl.nanovg.NanoVG.nvgResetScissor; import static org.lwjgl.nanovg.NanoVG.nvgRestore; import static org.lwjgl.nanovg.NanoVG.nvgRoundedRectVarying; -import static org.lwjgl.nanovg.NanoVG.nvgScissor; import static org.lwjgl.nanovg.NanoVG.nvgTextAlign; import static org.lwjgl.nanovg.NanoVG.nvgTextBounds; import static org.lwjgl.system.MemoryUtil.memFree; @@ -32,13 +29,10 @@ import com.spinyowl.spinygui.core.node.Element; import com.spinyowl.spinygui.core.node.Node; -import com.spinyowl.spinygui.core.node.layout.Edges; import com.spinyowl.spinygui.core.style.ResolvedStyle; import com.spinyowl.spinygui.core.style.types.HorizontalAlign; import com.spinyowl.spinygui.core.style.types.VerticalAlign; import java.nio.ByteBuffer; -import java.util.Iterator; -import java.util.LinkedList; import lombok.NoArgsConstructor; import lombok.NonNull; import org.joml.Vector2f; @@ -49,6 +43,8 @@ @NoArgsConstructor(access = PRIVATE) public final class NvgRenderUtils { + private static final NvgClipStack CLIP_STACK = new NvgClipStack(new NvgClipStack.NanoVgClipSink()); + public static float[] calculateTextBoundsRect( long context, Vector4f rect, @@ -183,7 +179,7 @@ public static void inScissor(long context, Node node, Runnable runnable) { * @param node node. */ public static void createScissor(long context, Node node) { - createScissorByParent(context, node.offsetParent()); + CLIP_STACK.create(context, node); } /** @@ -193,35 +189,7 @@ public static void createScissor(long context, Node node) { * @param parent parent node. */ public static void createScissorByParent(long context, Node parent) { - if (parent == null) return; - - var parents = new LinkedList(); - Node current = parent; - - while (current != null) { - parents.add(current); - current = current.offsetParent(); - } - - if (!parents.isEmpty()) { - Iterator descendingIterator = parents.descendingIterator(); - - current = descendingIterator.next(); - Edges border = current.asElement().box().border(); - - var pos = current.absolutePosition(); - var size = current.asElement().box().paddingBoxSize(); - nvgScissor(context, pos.x + border.left(), pos.y + border.top(), size.x, size.y); - - while (descendingIterator.hasNext()) { - current = descendingIterator.next(); - border = current.asElement().box().border(); - - pos = current.absolutePosition(); - size = current.asElement().box().paddingBoxSize(); - nvgIntersectScissor(context, pos.x + border.left(), pos.y + border.top(), size.x, size.y); - } - } + CLIP_STACK.createByParent(context, parent); } /** @@ -230,7 +198,7 @@ public static void createScissorByParent(long context, Node parent) { * @param context nanovg context pointer. */ public static void resetScissor(long context) { - nvgResetScissor(context); + CLIP_STACK.reset(context); } /** diff --git a/spinygui.core.backend.lwjgl.nanovg/src/test/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/util/NvgClipStackTest.java b/spinygui.core.backend.lwjgl.nanovg/src/test/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/util/NvgClipStackTest.java new file mode 100644 index 00000000..85358fb2 --- /dev/null +++ b/spinygui.core.backend.lwjgl.nanovg/src/test/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/util/NvgClipStackTest.java @@ -0,0 +1,103 @@ +package com.spinyowl.spinygui.core.backend.renderer.lwjgl.nanovg.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.spinyowl.spinygui.core.backend.renderer.lwjgl.nanovg.util.NvgClipStack.ClipSink; +import com.spinyowl.spinygui.core.node.Element; +import com.spinyowl.spinygui.core.node.NodeBuilder; +import com.spinyowl.spinygui.core.style.types.Overflow; +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.Test; + +class NvgClipStackTest { + + @Test + void create_doesNotClipVisibleAncestors() { + RecordingClipSink sink = new RecordingClipSink(); + NvgClipStack clipStack = new NvgClipStack(sink); + Element parent = element(10, 20, 100, 80, Overflow.VISIBLE); + Element child = element(15, 25, 30, 20, Overflow.VISIBLE); + parent.addChild(child); + child.offsetParent(parent); + + clipStack.create(1, child); + + assertTrue(sink.calls().isEmpty()); + } + + @Test + void create_clipsHiddenAutoAndScrollAncestorsToContentBox() { + RecordingClipSink sink = new RecordingClipSink(); + NvgClipStack clipStack = new NvgClipStack(sink); + Element hidden = element(10, 20, 100, 80, Overflow.HIDDEN); + hidden.box().padding().left(4); + hidden.box().padding().top(5); + hidden.box().border().left(1); + hidden.box().border().top(2); + Element auto = element(30, 40, 70, 60, Overflow.AUTO); + auto.box().padding().left(3); + auto.box().padding().top(4); + Element scroll = element(50, 60, 40, 30, Overflow.SCROLL); + Element child = element(0, 0, 10, 10, Overflow.VISIBLE); + hidden.addChild(auto); + auto.addChild(scroll); + scroll.addChild(child); + auto.offsetParent(hidden); + scroll.offsetParent(auto); + child.offsetParent(scroll); + + clipStack.create(1, child); + + assertEquals( + List.of( + "scissor(1,10.0,20.0,100.0,80.0)", + "intersect(1,35.0,53.0,70.0,60.0)", + "intersect(1,82.0,109.0,40.0,30.0)"), + sink.calls()); + } + + @Test + void reset_delegatesToSink() { + RecordingClipSink sink = new RecordingClipSink(); + NvgClipStack clipStack = new NvgClipStack(sink); + + clipStack.reset(7); + + assertEquals(List.of("reset(7)"), sink.calls()); + } + + private Element element(float x, float y, float width, float height, Overflow overflow) { + Element element = NodeBuilder.div(); + element.box().contentPosition(x, y); + element.box().contentSize(width, height); + element.resolvedStyle().overflowX(overflow); + element.resolvedStyle().overflowY(overflow); + return element; + } + + private static final class RecordingClipSink implements ClipSink { + + private final List calls = new ArrayList<>(); + + @Override + public void scissor(long context, float x, float y, float width, float height) { + calls.add("scissor(%d,%.1f,%.1f,%.1f,%.1f)".formatted(context, x, y, width, height)); + } + + @Override + public void intersectScissor(long context, float x, float y, float width, float height) { + calls.add("intersect(%d,%.1f,%.1f,%.1f,%.1f)".formatted(context, x, y, width, height)); + } + + @Override + public void reset(long context) { + calls.add("reset(%d)".formatted(context)); + } + + List calls() { + return calls; + } + } +} From 4e11be95805f9d0f36e81f56c610f68f74bf1aca Mon Sep 17 00:00:00 2001 From: SpinyOwl Date: Sun, 31 May 2026 00:01:34 +0200 Subject: [PATCH 027/121] Keep inline rendering aligned with scroll offsets --- .../scrollable-overflow-containers.md | 16 +++-- .../lwjgl/nanovg/NvgBorderRenderer.java | 6 +- .../lwjgl/nanovg/NvgElementRenderer.java | 6 +- .../lwjgl/nanovg/NvgTextRenderer.java | 10 ++- .../nanovg/NvgInlineFormattingOffsetTest.java | 64 +++++++++++++++++++ .../core/node/NodeAbsolutePositionTest.java | 44 +++++++++++++ 6 files changed, 132 insertions(+), 14 deletions(-) create mode 100644 spinygui.core.backend.lwjgl.nanovg/src/test/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/NvgInlineFormattingOffsetTest.java create mode 100644 spinygui.core/src/test/java/com/spinyowl/spinygui/core/node/NodeAbsolutePositionTest.java diff --git a/docs/features/scrollable-overflow-containers.md b/docs/features/scrollable-overflow-containers.md index bdc256d0..293a2968 100644 --- a/docs/features/scrollable-overflow-containers.md +++ b/docs/features/scrollable-overflow-containers.md @@ -149,20 +149,22 @@ Support CSS `overflow`, `overflow-x`, and `overflow-y` for block and flex elemen **Purpose:** Verify scroll offsets affect all render paths, including block children, flex children, text, and inline fragments. **Changes:** -- [ ] Audit `Node.absolutePosition()`, `NvgElementRenderer.inlineFormattingOffset`, and `NvgTextRenderer.inlineFormattingOffset` for double-subtraction or missing subtraction of ancestor scroll offsets. -- [ ] Add tests around absolute position calculations for nested scroll parents, including `scrollTop` and `scrollLeft`. -- [ ] Adjust inline rendering offset if inline text inside a scrolled block does not move with the block content. -- [ ] Confirm positioned descendants follow the existing `offsetParent` model and document any limitation for absolute-positioned children inside scroll containers. +- [x] Audit `Node.absolutePosition()`, `NvgElementRenderer.inlineFormattingOffset`, and `NvgTextRenderer.inlineFormattingOffset` for double-subtraction or missing subtraction of ancestor scroll offsets. +- [x] Add tests around absolute position calculations for nested scroll parents, including `scrollTop` and `scrollLeft`. +- [x] Adjust inline rendering offset if inline text inside a scrolled block does not move with the block content. +- [x] Confirm positioned descendants follow the existing `offsetParent` model and document any limitation for absolute-positioned children inside scroll containers. **Acceptance Checks:** -- [ ] Add a unit test where a child under a scrolled parent has `absolutePosition().y == unscrolledY - scrollTop`. -- [ ] Add a test covering nested scroll offsets accumulating from multiple ancestors. -- [ ] Run `.\gradlew.bat :spinygui.core:test`. +- [x] Add a unit test where a child under a scrolled parent has `absolutePosition().y == unscrolledY - scrollTop`. +- [x] Add a test covering nested scroll offsets accumulating from multiple ancestors. +- [x] Run `.\gradlew.bat :spinygui.core:test`. **Dependencies:** Steps 3, 5, and 6. **Risks:** Absolute-positioned descendants may use a different offset parent than their DOM parent. Do not silently change that model in this feature unless tests prove it is necessary for scroll correctness. +**Step 7 note:** Positioned descendants continue to follow the existing `offsetParent` chain. Scroll offsets accumulate along that chain, so descendants whose offset parent is not their DOM scroll container do not implicitly inherit that container's scroll offset. + ### Step 8: Add Demo Coverage and Documentation **Purpose:** Make the feature easy to manually verify and keep support docs accurate. diff --git a/spinygui.core.backend.lwjgl.nanovg/src/main/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/NvgBorderRenderer.java b/spinygui.core.backend.lwjgl.nanovg/src/main/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/NvgBorderRenderer.java index e3506cef..a004b0c2 100644 --- a/spinygui.core.backend.lwjgl.nanovg/src/main/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/NvgBorderRenderer.java +++ b/spinygui.core.backend.lwjgl.nanovg/src/main/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/NvgBorderRenderer.java @@ -47,11 +47,13 @@ public void render(Node node, long nanovg) { resetScissor(nanovg); } - private Vector2f inlineFormattingOffset(Element element) { + Vector2f inlineFormattingOffset(Element element) { Element parent = element.parent(); while (parent != null && Display.INLINE.equals(parent.resolvedStyle().display())) { parent = parent.parent(); } - return parent == null ? new Vector2f() : parent.absolutePosition(); + return parent == null + ? new Vector2f() + : parent.absolutePosition().sub(parent.scrollLeft(), parent.scrollTop()); } } diff --git a/spinygui.core.backend.lwjgl.nanovg/src/main/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/NvgElementRenderer.java b/spinygui.core.backend.lwjgl.nanovg/src/main/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/NvgElementRenderer.java index c4788d24..938a54f1 100644 --- a/spinygui.core.backend.lwjgl.nanovg/src/main/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/NvgElementRenderer.java +++ b/spinygui.core.backend.lwjgl.nanovg/src/main/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/NvgElementRenderer.java @@ -51,11 +51,13 @@ public void render(Node node, long nanovg) { } } - private Vector2f inlineFormattingOffset(Element element) { + Vector2f inlineFormattingOffset(Element element) { Element parent = element.parent(); while (parent != null && Display.INLINE.equals(parent.resolvedStyle().display())) { parent = parent.parent(); } - return parent == null ? new Vector2f() : parent.absolutePosition(); + return parent == null + ? new Vector2f() + : parent.absolutePosition().sub(parent.scrollLeft(), parent.scrollTop()); } } diff --git a/spinygui.core.backend.lwjgl.nanovg/src/main/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/NvgTextRenderer.java b/spinygui.core.backend.lwjgl.nanovg/src/main/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/NvgTextRenderer.java index 8c98218f..30c9315d 100644 --- a/spinygui.core.backend.lwjgl.nanovg/src/main/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/NvgTextRenderer.java +++ b/spinygui.core.backend.lwjgl.nanovg/src/main/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/NvgTextRenderer.java @@ -51,15 +51,19 @@ public void render(Node node, long nanovg) { resetScissor(nanovg); } - private Vector2f inlineFormattingOffset(Text text) { + Vector2f inlineFormattingOffset(Text text) { Element parent = text.parent(); while (parent != null && Display.INLINE.equals(parent.resolvedStyle().display())) { parent = parent.parent(); } if (parent != null) { - return parent.absolutePosition(); + return parent.absolutePosition().sub(parent.scrollLeft(), parent.scrollTop()); } - return text.offsetParent() == null ? new Vector2f() : text.offsetParent().absolutePosition(); + return text.offsetParent() == null + ? new Vector2f() + : text.offsetParent() + .absolutePosition() + .sub(text.offsetParent().scrollLeft(), text.offsetParent().scrollTop()); } private void renderFragment(InlineFragment fragment, long nanovg, Vector2f offset) { diff --git a/spinygui.core.backend.lwjgl.nanovg/src/test/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/NvgInlineFormattingOffsetTest.java b/spinygui.core.backend.lwjgl.nanovg/src/test/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/NvgInlineFormattingOffsetTest.java new file mode 100644 index 00000000..0d03dbf8 --- /dev/null +++ b/spinygui.core.backend.lwjgl.nanovg/src/test/java/com/spinyowl/spinygui/core/backend/renderer/lwjgl/nanovg/NvgInlineFormattingOffsetTest.java @@ -0,0 +1,64 @@ +package com.spinyowl.spinygui.core.backend.renderer.lwjgl.nanovg; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.spinyowl.spinygui.core.node.Element; +import com.spinyowl.spinygui.core.node.NodeBuilder; +import com.spinyowl.spinygui.core.node.Text; +import com.spinyowl.spinygui.core.style.types.Display; +import org.joml.Vector2f; +import org.junit.jupiter.api.Test; + +class NvgInlineFormattingOffsetTest { + + @Test + void elementInlineFormattingOffset_subtractsContainingBlockScroll() { + Element block = scrolledBlock(); + Element inline = inlineElement(); + block.addChild(inline); + + Vector2f offset = new NvgElementRenderer().inlineFormattingOffset(inline); + + assertEquals(80, offset.x()); + assertEquals(35, offset.y()); + } + + @Test + void textInlineFormattingOffset_subtractsContainingBlockScroll() { + Element block = scrolledBlock(); + Text text = new Text("content"); + block.addChild(text); + + Vector2f offset = new NvgTextRenderer().inlineFormattingOffset(text); + + assertEquals(80, offset.x()); + assertEquals(35, offset.y()); + } + + @Test + void borderInlineFormattingOffset_subtractsContainingBlockScroll() { + Element block = scrolledBlock(); + Element inline = inlineElement(); + block.addChild(inline); + + Vector2f offset = new NvgBorderRenderer().inlineFormattingOffset(inline); + + assertEquals(80, offset.x()); + assertEquals(35, offset.y()); + } + + private Element scrolledBlock() { + Element block = NodeBuilder.div(); + block.box().contentPosition(100, 60); + block.box().contentSize(200, 100); + block.scrollLeft(20); + block.scrollTop(25); + return block; + } + + private Element inlineElement() { + Element inline = NodeBuilder.div(); + inline.resolvedStyle().display(Display.INLINE); + return inline; + } +} diff --git a/spinygui.core/src/test/java/com/spinyowl/spinygui/core/node/NodeAbsolutePositionTest.java b/spinygui.core/src/test/java/com/spinyowl/spinygui/core/node/NodeAbsolutePositionTest.java new file mode 100644 index 00000000..c678db09 --- /dev/null +++ b/spinygui.core/src/test/java/com/spinyowl/spinygui/core/node/NodeAbsolutePositionTest.java @@ -0,0 +1,44 @@ +package com.spinyowl.spinygui.core.node; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +class NodeAbsolutePositionTest { + + @Test + void absolutePosition_subtractsOffsetParentScroll() { + Element parent = element(10, 20); + parent.scrollTop(30); + Element child = element(40, 80); + parent.addChild(child); + child.offsetParent(parent); + + assertEquals(70, child.absolutePosition().y()); + } + + @Test + void absolutePosition_accumulatesNestedScrollOffsets() { + Element outer = element(100, 50); + outer.scrollLeft(10); + outer.scrollTop(20); + Element inner = element(30, 40); + inner.scrollLeft(5); + inner.scrollTop(8); + Element child = element(70, 90); + outer.addChild(inner); + inner.addChild(child); + inner.offsetParent(outer); + child.offsetParent(inner); + + assertEquals(185, child.absolutePosition().x()); + assertEquals(152, child.absolutePosition().y()); + } + + private Element element(float x, float y) { + Element element = NodeBuilder.div(); + element.box().contentPosition(x, y); + element.box().contentSize(10, 10); + return element; + } +} From 823e25bfcf1aba7401fa0b2b4eb6eaa68200b72b Mon Sep 17 00:00:00 2001 From: SpinyOwl Date: Sun, 31 May 2026 00:38:39 +0200 Subject: [PATCH 028/121] Add overflow demo and finalize support docs --- docs/features/css-properties-support.md | 8 +- .../scrollable-overflow-containers.md | 12 +-- .../spinygui/core/style/ResolvedStyle.java | 8 +- .../style/manager/FlexStyleManagerTest.java | 45 +++++++++++ .../demo/complex/OverflowExample.java | 37 +++++++++ .../com/spinyowl/spinygui/demo/index.html | 2 +- .../spinyowl/spinygui/demo/overflow-demo.css | 77 +++++++++++++++++++ .../spinyowl/spinygui/demo/overflow-demo.xml | 24 ++++++ 8 files changed, 199 insertions(+), 14 deletions(-) create mode 100644 spinygui.core/src/test/java/com/spinyowl/spinygui/core/style/manager/FlexStyleManagerTest.java create mode 100644 spinygui.demo.complex/src/main/java/com/spinyowl/spinygui/demo/complex/OverflowExample.java create mode 100644 spinygui.demo.complex/src/main/resources/com/spinyowl/spinygui/demo/overflow-demo.css create mode 100644 spinygui.demo.complex/src/main/resources/com/spinyowl/spinygui/demo/overflow-demo.xml diff --git a/docs/features/css-properties-support.md b/docs/features/css-properties-support.md index 64779595..9ccc8a2d 100644 --- a/docs/features/css-properties-support.md +++ b/docs/features/css-properties-support.md @@ -52,7 +52,7 @@ Approximate implementation estimates for unchecked entries: | `object-fit`, `object-position` | M | Replaced element/image sizing and drawing behavior. | | `order` | M | Flex item ordering plus layout invalidation and traversal implications. | | `outline`, `outline-color`, `outline-offset`, `outline-style`, `outline-width` | M | Outline property parsing and paint path outside border box. | -| `overflow`, `overflow-x`, `overflow-y` | L | Clipping, scroll container behavior, hit-testing, and possibly scroll input. | +| `overflow`, `overflow-x`, `overflow-y` | Supported | Supports `visible`, `hidden`, `auto`, and `scroll` for block/flex scroll containers, including scroll input, clipping, layout metrics, and hit-testing. | | `perspective`, `perspective-origin`, `transform`, `transform-origin`, `transform-style` | XL | Transform matrices, coordinate conversion, stacking contexts, renderer transforms, and hit-testing. | | `position: fixed` | L | Viewport-relative containing block, scroll behavior, stacking, and event coordinate handling. | | `resize` | L | User interaction, constraints, layout invalidation, and cursor behavior. | @@ -244,9 +244,9 @@ Checklist of CSS properties: - [ ] `outline-offset` - [ ] `outline-style` - [ ] `outline-width` -- [ ] `overflow` -- [ ] `overflow-x` -- [ ] `overflow-y` +- [x] `overflow` +- [x] `overflow-x` +- [x] `overflow-y` - [x] `overflow-wrap` - [x] `padding` - [x] `padding-bottom` diff --git a/docs/features/scrollable-overflow-containers.md b/docs/features/scrollable-overflow-containers.md index 293a2968..3c6a90da 100644 --- a/docs/features/scrollable-overflow-containers.md +++ b/docs/features/scrollable-overflow-containers.md @@ -169,19 +169,21 @@ Support CSS `overflow`, `overflow-x`, and `overflow-y` for block and flex elemen **Purpose:** Make the feature easy to manually verify and keep support docs accurate. **Changes:** -- [ ] Update `docs/features/css-properties-support.md` to mark `overflow`, `overflow-x`, and `overflow-y` as supported after tests pass. -- [ ] Add or update a demo HTML/CSS resource with nested vertical and horizontal scroll containers. -- [ ] Include one `overflow: visible` case in the demo to catch accidental clipping regressions. +- [x] Update `docs/features/css-properties-support.md` to mark `overflow`, `overflow-x`, and `overflow-y` as supported after tests pass. +- [x] Add or update a demo XML/CSS resource with nested vertical and horizontal scroll containers. +- [x] Include one `overflow: visible` case in the demo to catch accidental clipping regressions. **Acceptance Checks:** - [ ] Run the relevant demo and manually verify: vertical scroll, horizontal scroll, nested scroll chaining, clipped content, and visible overflow. -- [ ] Run `.\gradlew.bat test` for full validation. -- [ ] Confirm no unrelated generated files or build artifacts are included in the change. +- [x] Run `.\gradlew.bat test` for full validation. +- [x] Confirm no unrelated generated files or build artifacts are included in the change. **Dependencies:** Steps 1 through 7. **Risks:** Manual demo verification depends on the local backend/OpenGL environment. If unavailable, record that limitation and rely on unit/backend tests for CI coverage. +**Step 8 note:** Automated validation passed, but interactive NanoVG/OpenGL demo verification was not performed in this non-interactive run. `OverflowExample` loads separate XML and CSS resources for local manual verification because inline ``, so parsed `winframe` structures can carry their own CSS in `Frame.styleSheets()` and work without separate caller-side stylesheet parsing. + +## Non-Goals +- External stylesheet loading through `` or CSS `@import`. +- Browser-complete `` semantics. +- Dynamic stylesheet mutation APIs after parsing. +- Supporting `
A
` returns a `Frame` with one stylesheet and one child `div`. +- [ ] Parser tests prove multiple ` @@ -74,6 +103,15 @@ Hello world. Lorem ipsum dolor.s Hello World
+
+ word-wrap: Supercalifragilisticexpialidocious +
+
+ word-break: Supercalifragilisticexpialidocious +
+
+ before inline block after +
From a741a1494919f698cd3ce37b689c0fc7244cc88c Mon Sep 17 00:00:00 2001 From: SpinyOwl Date: Wed, 17 Jun 2026 02:25:20 +0200 Subject: [PATCH 056/121] Center flex main menu and fix auto main-axis sizing --- gui-test-html-app/app.html | 276 ++++++++++++++---- .../spinygui/core/layout/impl/FlexLayout.java | 65 +++-- .../core/layout/impl/FlexLayoutTest.java | 40 +++ spinygui.demo.complex/build.gradle.kts | 8 + .../demo/complex/MainMenuExample.java | 102 +++++++ .../com/spinyowl/spinygui/demo/main-menu.css | 195 +++++++++++++ .../com/spinyowl/spinygui/demo/main-menu.xml | 32 ++ .../demo/complex/MainMenuExampleTest.java | 76 +++++ 8 files changed, 707 insertions(+), 87 deletions(-) create mode 100644 spinygui.demo.complex/src/main/java/com/spinyowl/spinygui/demo/complex/MainMenuExample.java create mode 100644 spinygui.demo.complex/src/main/resources/com/spinyowl/spinygui/demo/main-menu.css create mode 100644 spinygui.demo.complex/src/main/resources/com/spinyowl/spinygui/demo/main-menu.xml create mode 100644 spinygui.demo.complex/src/test/java/com/spinyowl/spinygui/demo/complex/MainMenuExampleTest.java diff --git a/gui-test-html-app/app.html b/gui-test-html-app/app.html index 3bdb9090..6d46bc72 100644 --- a/gui-test-html-app/app.html +++ b/gui-test-html-app/app.html @@ -13,6 +13,8 @@ html, body { overflow: hidden; + width: 100%; + height: 100%; } :root { --winframe-width: 100%; @@ -42,89 +44,239 @@ } /* styles from app */ - winframe { - overflow: scroll; - box-sizing: border-box; /* default behaviour */ + + + .main-menu { + background-color: transparent; + box-sizing: border-box; + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; } - winframe * { - border: 8px solid #8c8c8c; - box-sizing: border-box; /* default behaviour */ - overflow: hidden; /* default behaviour */ + + .main-menu-center { + width: 420px; + padding: 22px; + box-sizing: border-box; + background-color: rgba(8, 12, 16, 0.92); + border: 1px solid rgba(220, 230, 235, 0.22); } - winframe:hover, - winframe *:hover { - border: 8px solid #fc3131; + + .main-menu-title { + display: block; + margin-bottom: 12px; + } + + .main-menu-title-text { + display: block; + font-size: 30px; + font-weight: 800; + color: #eef3f5; + white-space: nowrap; + overflow: hidden; } - #t2 { - width: 500px; + + .main-menu-title-subtext { + display: block; + font-size: 14px; + color: #8fd4ff; + white-space: nowrap; + overflow: hidden; } - .wrap-demo { + + .main-menu-status { display: block; - width: 120px; - font-size: 16px; - border-color: #45AAFF; + min-height: 16px; + padding: 0 2px; + font-size: 13px; + color: #f5df5f; + white-space: nowrap; + overflow: hidden; + } + + .main-menu-player { + display: block; + margin: 4px 0 10px; + padding: 8px; + background-color: rgba(18, 24, 28, 0.94); + border: 1px solid rgba(220, 230, 235, 0.16); + } + + .main-menu-player-label { + display: block; + font-size: 13px; + font-weight: 700; + color: #8fd4ff; + white-space: nowrap; + overflow: hidden; + } + + .main-menu-player-input { + display: block; + width: 100%; + height: 28px; + box-sizing: border-box; + padding: 4px 6px; + margin-top: 6px; + margin-bottom: 6px; + font-size: 13px; + color: #eef3f5; + background-color: rgba(7, 11, 14, 0.96); + border: 1px solid rgba(220, 230, 235, 0.22); + } + + .main-menu-player-validation { + display: block; + font-size: 12px; + color: #c0c8cc; + min-height: 16px; + white-space: nowrap; + overflow: hidden; + } + + .main-menu-action-panel { + width: 100%; + padding: 0; + } + + .main-menu-action { + display: block; + box-sizing: border-box; + width: 100%; + min-height: 54px; + padding: 8px; + margin-bottom: 6px; + text-align: left; + font-family: inherit; + color: #eef3f5; + cursor: pointer; + overflow: hidden; + border: 1px solid rgba(220, 230, 235, 0.16); + } + + .main-menu-action.disabled { + background-color: rgba(28, 34, 40, 0.9); + color: #c8d2d8; + cursor: default; + } + + .main-menu-action.selected { + border: 1px solid #8fd4ff; + } + + button.main-menu-action { + background-color: rgba(28, 34, 40, 0.9); + } + + button.main-menu-action:hover { + border-color: #b8e5ff; + background-color: rgba(44, 68, 78, 0.98); } - .word-wrap-demo { - word-wrap: break-word; + + button.main-menu-action:active { + border-color: #f5df5f; + background-color: rgba(64, 76, 46, 0.98); } - .word-break-demo { - word-break: break-all; - border-color: #AA45FF; + + button.main-menu-action.selected { + background-color: rgba(36, 52, 62, 0.94); } - .inline-block-demo { + + .main-menu-action-label { display: block; - width: 320px; font-size: 16px; - border-color: #2dd4bf; + font-weight: 700; + color: #eef3f5; + white-space: nowrap; + overflow: hidden; } - .inline-chip { - display: inline-block; - width: auto; - padding: 4px 8px; - border: 3px solid #0f766e; - background-color: #ccfbf1; + + .main-menu-action-status { + display: block; + font-size: 13px; + color: #8fd4ff; + white-space: nowrap; + overflow: hidden; + } + + .main-menu-action-reason { + display: block; + font-size: 12px; + color: #c0c8cc; + white-space: nowrap; overflow: hidden; } + .main-menu-endpoint-wrap { + display: block; + margin-top: 8px; + width: 100%; + } + + .main-menu-endpoint-input { + display: block; + width: 300px; + height: 28px; + box-sizing: border-box; + padding: 4px 6px; + margin-bottom: 6px; + font-size: 13px; + color: #eef3f5; + background-color: rgba(7, 11, 14, 0.96); + border: 1px solid rgba(220, 230, 235, 0.22); + } + + .main-menu-endpoint-connect { + display: block; + width: 86px; + height: 28px; + box-sizing: border-box; + padding: 4px 8px; + font-size: 13px; + font-weight: 700; + color: #eef3f5; + background-color: rgba(36, 52, 62, 0.94); + border: 1px solid rgba(143, 212, 255, 0.75); + } + + - -
-
c1 -
c11 Cfjg
-
c12
-
-
- Hello world. Lorem ipsum dolor. - Hello world. Lorem ipsum dolor. - Hello world. Lorem ipsum dolor.s - Hello World + +