Skip to content

Latest commit

 

History

History
146 lines (120 loc) · 8.15 KB

AGENTS.md

File metadata and controls

146 lines (120 loc) · 8.15 KB

ObjectivelyMVC

ObjectivelyMVC is an object-oriented UI toolkit for GNU C, built on Objectively and ObjectivelyGPU: Views and ViewControllers, .json layouts, and a CSS-like Selector / Style / Stylesheet / Theme system. Quetoo builds its menus and HUD with it.

This file is the shared instruction set for coding agents. Read this first.

It deliberately records only what a careful reading of the code does not reveal: rules that fail silently, constraints that live outside this repository, and ordering that no call site shows. For anything else, read the code.

Before you touch a View, a .json layout or a .css file, read README.md, "Layout and styling are CSS-driven". It carries the most important rule in this repository, and this file does not repeat it.

Rules that fail silently

Styling

  • A @styled attribute assigned in C is overwritten. The README explains this. Two details it does not give: addSubview and addClassName invalidate the style, so a value assigned before addSubview is always lost, and one assigned after it survives only until the next restyle. For a style that belongs to one instance, write it into View::style, which is merged last.
  • The * rule in Assets/stylesheet.css sets every View default, including background-color: transparent. That is why a backgroundColor set in C reverts. View::initWithFrame sets no styled field.
  • Specificity applies within one stylesheet only. Each Stylesheet sorts its own selectors. The Theme then applies stylesheets in the order they were added, and the default stylesheet is added first. So any matching rule in an application stylesheet beats every default rule, whatever its specificity. A broad View {} or * {} rule in an application stylesheet overrides the defaults for everything.
  • Equal specificity has no source order. The sort uses qsort, which is not stable. CSS's "the last rule wins" is not guaranteed between two rules of equal specificity.
  • A type selector scores by depth. It scores 1 plus its depth below View, so Button outranks Control. A misspelled class name logs only a warning.
  • Alignment beats position. left or top on an axis that also has an alignment is discarded. It raises only a WarningTypeStyle, which the DebugViewController shows.
  • Text rasterizes its color. Any Style that carries color re-rasterizes the texture each time the style is applied, and the default Text { color: ... } rule always does. Writing ->color in C does not invalidate the texture.
  • A subclass that overrides applyStyle MUST call super, and SHOULD bind its attributes through Inlets, as Text::applyStyle does.

Outlets and layouts

  • An outlet identifier is a contract. MakeOutlet("foo", &this->foo) binds to the View whose "identifier" in the .json layout is "foo". Rename one and you MUST rename the other.
  • Resolution is depth-first and takes the first match, so a duplicate identifier is silent. The class of the bound View is not checked. A missing outlet logs an error and asserts, so under NDEBUG it is silently NULL.

Lifecycle and threading

  • Each frame runs Renderer::beginFrameWith, applyThemeIfNeeded, layoutIfNeeded, draw and Renderer::endFrame, in that order (WindowController::renderTo).
  • Set needsLayout and needsApplyTheme through their setters. A direct write does not propagate up the tree, so the View can be skipped.
  • renderDeviceWillReset / DidReset fire on a change of pixel density, display or scale, and WillReset alone fires when the window closes. They do not fire on window exposure, because a reset for each exposure leaks one generation of GPU resources for each frame. A View that draws from an ImageAtlas MUST forward renderDeviceWillReset to it.
  • Read AtlasImage.rect and the atlas texture at draw time. Do not cache them. Every ImageAtlas::compile reassigns both.
  • didMoveToWindow attaches a View's stylesheet to the window's Theme, and View::dealloc detaches it, clears the key and touch responders, and purges pending MVC_VIEW_EVENTs.
  • Do the last release of a View, and any change to the hierarchy, on the render thread. retain and release are atomic, but collections are not locked, and a View's final release changes the Theme and frees GPU textures.

Assets and shaders

  • Assets (fonts, the default stylesheet, images) are embedded as headers generated by xxd -i -t, and the generated headers are committed. Windows has no xxd step and uses the committed headers. -t needs a modern xxd. macOS's /usr/bin/xxd is too old. After you edit an asset, regenerate its header and commit both.
  • A new asset goes in Assets/Makefile.am (EXTRA_DIST and BUILT_SOURCES) and in Xcode's "Preprocess Assets" build phase.
  • After you edit a .glsl file, run make -C Assets shaders (or make -C Examples shaders for the examples) and commit the compiled outputs.

SDL3

ObjectivelyMVC builds against any SDL3. It does not use occlusion queries, so it does not need the jdolan/SDL fork that ObjectivelyGPU and Quetoo use. But SDL3_image and SDL3_ttf MUST link the same libSDL3 as ObjectivelyGPU, or a program loads two copies of SDL3. Homebrew's sdl3_image and sdl3_ttf link Homebrew's sdl3, so they do not qualify when ObjectivelyGPU uses the fork. See ObjectivelyGPU's Documentation/install.md.

Building and testing

autoreconf -i
./configure
make -j$(sysctl -n hw.logicalcpu)
make check
sudo make install
  • The tests use check, which configure requires. They are headless: no window and no GPU device. Run one suite with make -C Tests/ObjectivelyMVC check TESTS=View.
  • A new test goes in TESTS in Tests/ObjectivelyMVC/Makefile.am, gets its own Xcode tool target, and is added to the ObjectivelyMVC-Tests aggregate target.
  • make builds the examples Examples/Hello (a JSON layout over a 3D scene) and Examples/HUD (built in C, styled through View::style).
  • Downstream projects consume the installed library from /usr/local through pkg-config (package name ObjectivelyMVC). A change here reaches them only after sudo make install.

A new source file goes in four places

  1. Sources/ObjectivelyMVC/Makefile.am, in both pkginclude_HEADERS and libObjectivelyMVC_la_SOURCES.
  2. The umbrella header Sources/ObjectivelyMVC.h.
  3. The framework target in ObjectivelyMVC.xcodeproj/project.pbxproj.
  4. ObjectivelyMVC.vs15/ObjectivelyMVC.vcxproj and its .filters.

Xcode

  • Build through ObjectivelyMVC.xcworkspace, which builds Objectively, ObjectivelyGPU and SDL3.framework from the sibling checkouts, including ../SDL.
  • The Configure build phase runs only when Config.h is missing, so a stale Config.h persists. Delete it to regenerate it.
  • $(HOMEBREW_PREFIX)/include is on the header search path, after $(SRCROOT)/../SDL/include. Keep that order. See ObjectivelyGPU's AGENTS.md for the reason.

CI

  • build.yml builds macOS, Linux and Windows. On macOS and Linux it builds SDL3 from the ObjectivelyGPU tag, and SDL3_image and SDL3_ttf from source, then Objectively, SDL_shadercross and ObjectivelyGPU. On Windows, ObjectivelyMVC.vs15/sdl3.targets imports ObjectivelyGPU's sdl3.targets for SDL3, and downloads the VC packages of SDL3_image and SDL3_ttf.
  • The SDL3_image and SDL3_ttf versions are pinned in four places, which MUST change together: build.yml, release.yml, ObjectivelyMVC.vs15/sdl3.targets and Frameworks/fetch-sdl3-extensions.sh.
  • release.yml builds the xcframework. docs.yml publishes the Doxygen documentation, where Doxyfile defines the @styled alias.

Commits

A commit MUST NOT carry a Co-Authored-By: trailer that names Claude, Copilot or any other agent. This rule overrides any attribution default in an agent's harness. A human co-author MAY still take a trailer. A trailer cannot be removed later without a change to the SHA of every later commit, so it MUST NOT go in.