{"id":16014864,"url":"https://github.com/kitten/moomin","last_synced_at":"2025-10-25T14:09:56.743Z","repository":{"id":57302689,"uuid":"151273426","full_name":"kitten/moomin","owner":"kitten","description":"An experimental JSX/component framework around Reprocessing for Reason","archived":false,"fork":false,"pushed_at":"2018-10-04T09:43:47.000Z","size":142,"stargazers_count":17,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2024-10-19T05:43:07.660Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"OCaml","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kitten.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-10-02T14:59:08.000Z","updated_at":"2021-08-31T03:08:13.000Z","dependencies_parsed_at":"2022-09-20T17:14:33.839Z","dependency_job_id":null,"html_url":"https://github.com/kitten/moomin","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kitten%2Fmoomin","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kitten%2Fmoomin/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kitten%2Fmoomin/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kitten%2Fmoomin/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kitten","download_url":"https://codeload.github.com/kitten/moomin/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":221704895,"owners_count":16866852,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-08T15:05:22.243Z","updated_at":"2025-10-25T14:09:56.645Z","avatar_url":"https://github.com/kitten.png","language":"OCaml","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Moomin\n\nAn **experimental** framework around [Reprocessing](https://github.com/Schmavery/reprocessing) for Reason,\nallowing games to be written with components and JSX.\n\n\u003e **NOTE:** This is an experimental stage project. It's basic functionality is to provide components\n\u003e that can render SVG-like elements with Reprocessing.\n\u003e It's currently just a lean alternative API around Reprocessing that doesn't do much more than to\n\u003e organise state in components and to allow JSX instead of direct function calls.\n\n\u003cbr\u003e\n\n\u003ccenter\u003e\u003cimg alt=\"Moomin\" src=\"/docs/moomin.jpg?raw=true\" width=\"300px\" /\u003e\u003c/center\u003e\n\n**[Check out CodeSandbox for a quick example](https://codesandbox.io/s/ypx4855m09)**\n\n## Why?\n\n[Reprocessing](https://github.com/Schmavery/reprocessing) is an excellent library for writing games\nwith as little code as is possible. Out of sheer interest, I wanted to see whether it was feasible\nto structure games like you would structure a React app.\n\nThe more interesting implication of this is that someone familiar with React will be able to write\ncleaner games, if they have access to a component/element tree and an SVG-like JSX syntax.\n\nOn the other hand it does _complicate_ Reprocessing's minimal and elegant API, and you might find\nit to add mental overhead.\n\n## How?\n\nThis library follows [ReasonReact's](https://reasonml.github.io/reason-react/docs/en/jsx) structure\nand utilises Reason's built-in JSX transpilation to create elements of its own.\nInstead of creating elements and components for React to use, it instead has its own element structure.\n\nOnce elements in Moomin are created however, it follows the React intuition quite closely. Every\nelement is rendered at 60 FPS (Not when state changes!) and preserves its state across renders.\n\nWhen an element unmounts (or its key changes, like in React) its state is thrown away. The usual\nlifecycle rules of React apply and ReasonReact's API is adopted vaguely, but not closely.\n\n## Table of Contents\n\n\u003c!-- START doctoc generated TOC please keep comment here to allow auto update --\u003e\n\u003c!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --\u003e\n\n- [Installation](#installation)\n- [Usage](#usage)\n  - [Basic Example](#basic-example)\n  - [Stateful Example](#stateful-example)\n- [Basics](#basics)\n  - [Components](#components)\n  - [Lifecycles](#lifecycles)\n  - [\"DOM\" Elements](#dom-elements)\n- [Shortcomings / Plans](#shortcomings--plans)\n- [API](#api)\n  - [Basic JSX](#basic-jsx)\n  - [Base Elements Props](#base-elements-props)\n  - [Elements](#elements)\n    - [`\u003cg\u003e`](#g)\n    - [`\u003crect\u003e`](#rect)\n    - [`\u003cline\u003e`](#line)\n    - [`\u003ctriangle\u003e`](#triangle)\n    - [`\u003cellipse\u003e`](#ellipse)\n    - [`\u003ccircle\u003e`](#circle)\n    - [`\u003ctext\u003e`](#text)\n    - [`\u003cimage\u003e`](#image)\n  - [Component Creation](#component-creation)\n  - [Component \u0026 Element Types](#component--element-types)\n    - [`selfT`](#selft)\n    - [`componentT`](#componentt)\n  - [Component API](#component-api)\n    - [`initialState`](#initialstate)\n    - [`willUpdate`](#willupdate)\n    - [`render`](#render)\n    - [`didMount`](#didmount)\n    - [`didUpdate`](#didupdate)\n    - [`willUnmount`](#willunmount)\n    - [`reducer`](#reducer)\n  - [Standard Components](#standard-components)\n    - [`\u003cStd.Setup\u003e`](#stdsetup)\n  - [Reprocessing APIs](#reprocessing-apis)\n- [LICENSE](#license)\n\n\u003c!-- END doctoc generated TOC please keep comment here to allow auto update --\u003e\n\n## Installation\n\nThis package is distributed via npm which is bundled with node and\nshould be installed as one of your project's `dependencies`:\n\n```sh\n# npm\nnpm install --save moomin\n# yarn\nyarn add moomin\n```\n\nAfter installing the package, don't forget to add it to your `bsconfig.json` and\nmake sure to enable Reason's `react-jsx: 2` mode:\n\n```diff\n{\n  \"name\": \"\u003cyour name\u003e\",\n  \"version\": \"0.1.0\",\n  \"sources\": [\"src\"],\n  \"bsc-flags\": [\"-bs-super-errors\"],\n  \"bs-dependencies\": [\n+    \"moomin\"\n  ],\n+  \"refmt\": 3,\n+  \"reason\": {\n+    \"react-jsx\": 2\n+  }\n}\n```\n\n\u003e This package does not depend on Reprocessing directly as it vendors it\n\u003e for now.\n\u003e\n\u003e - Please make sure not to install and add it to your `bsconfig.json`.\n\u003e - Please make sure not to add ReasonReact as well, as its module names are taken up by Moomin.\n\n## Usage\n\nA more extensive example can also be found on [Codesandbox](https://codesandbox.io/s/ypx4855m09).\n\n### Basic Example\n\nHere's a simple example with a single component rendering two rectangles on screen:\n\n```reason\nopen Moomin;\n\nmodule Example = {\n  let component = ReasonReact.statelessComponent(\"Example\");\n\n  let make = (~x, ~y, _children) =\u003e {\n    ...component,\n    render: self =\u003e {\n      \u003c\u003e\n        \u003crect x={x} y={y} width={50.} height={50.} fill={Colors.blue} /\u003e\n        \u003crect x={x} y={y +. 100.} width={50.} height={50.} fill={Colors.blue} /\u003e\n      \u003c/\u003e\n    }\n  };\n};\n\nrun(\n  \u003cStd.Setup width={200} height={200} background={Colors.white}\u003e\n    \u003cExample x={25.} y={25.} /\u003e\n    \u003cExample x={125.} y={25.} /\u003e\n  \u003c/Std.Setup\u003e\n);\n```\n\nAs you can see here, the API closely follows `ReasonReact`'s API, with the familiar\ncomponent creation and JSX.\n\nThe `render` lifecycle in this example returns two `\u003crect\u003e` elements inside a fragment.\nSimilar to SVG we'd also be able to wrap them inside a `\u003cg\u003e`, which allows for some\ntransformations.\n\nLastly the `run` function accepts some JSX elements and starts the Reprocessing\nrender loop with all elements' lifecycles.\n\n### Stateful Example\n\nStateful components (\"reducer components\") follow the same practices.\n\n```reason\nmodule Square = {\n  type state = {\n    rotate: float\n  };\n\n  let component = ReasonReact.reducerComponent(\"Square\");\n\n  let make = (_children) =\u003e {\n    ...component,\n    initialState: _glEnv =\u003e { rotate: 0. },\n    reducer: (_action: unit, state) =\u003e state,\n    willUpdate: self =\u003e { rotate: self.state.rotate +. 0.1 },\n    render: self =\u003e {\n      \u003crect\n        x={5.}\n        y={5.}\n        width={20.}\n        height={20.}\n        rotate={self.state.rotate}\n        fill={Colors.black}\n      /\u003e\n    }\n  };\n};\n```\n\nYou might notice that some lifecycles are different from ReasonReact's ones.\nDue to the fact that render cycles in Moomin occur at up to 60 times per second,\nthis also means that some lifecycles from ReasonReact don't quite fit this usecase.\n\nMore on this in the next section, \"Basics\".\n\n## Basics\n\n### Components\n\nSimilarly to `ReasonReact` there's helper functions to create a component\n\"template\". In Moomin there's (currently) only two:\n\n- `ReasonReact.statelessComponent`\n- `ReasonReact.reducerComponent`.\n\nThese two functions accept a _component name_ as their only argument. Unlike in\n`ReasonReact` these names must be unique and are not for debugging only, as they're\nalso used for the reconciliation process.\n\nThe last two examples already illustrate how to create stateless and stateful components.\n\n### Lifecycles\n\nMoomin's components have different lifecycle methods from `ReasonReact`'s ones. They're\nsimilar but slightly different. To be specific there are:\n\n- `initialState`\n- `willUpdate`\n- `render`\n- `didMount`\n- `didUpdate`\n- `willUnmount`\n\nSome of these might already imply their general use (more on that in the API section).\nAll of these methods are called synchronously during rendering, so there's no special\nconsideration being made for concurrency, hence `willUnmount` is just a simple lifecycle\nmethod for instance.\n\n### \"DOM\" Elements\n\nMoomin also exposes a `ReactDOMRe` module. It obviously doesn't render to the DOM, but it\nhas some elements that might remind you of SVG elements, namely:\n\n- `\u003crect\u003e`\n- `\u003cimage\u003e`\n- `\u003cline\u003e`\n- `\u003ctriangle\u003e`\n- `\u003cellipse\u003e`\n- `\u003ccircle\u003e`\n- `\u003ctext\u003e`\n- `\u003cg\u003e`\n\nNot all SVG elements have been implemented and some (Looking at you `\u003ctriangle\u003e`!) are\nnot SVG elements at all.\n\n## Shortcomings / Plans\n\n\u003e Note: This is a section on Moomin only. If you're trying to understand how Reprocessing\n\u003e differs from Processing [read their section on the matter](https://github.com/Schmavery/reprocessing#some-differences-from-processing).\n\n- Complex SVG elements have not been implemented. `\u003cpath\u003e` for instance\n- There's no element for tilemaps yet\n- There's no new color utilities for hex colours and more\n- Animations are a consideration, but efforts aren't complete yet (See `moomin_animated.re`)\n- Events are a consideration, but haven't been implemented yet\n- There's no components in `Std` for some standard timing or input events\n\n## API\n\n### Basic JSX\n\nThe basic use of components and JSX follows ReasonReact. Take a look at their\n[docs on JSX](https://reasonml.github.io/reason-react/docs/en/jsx) for more information.\n\nThe entrypoint for your game will always be the `run()` function, which accepts a JSX\nelement.\n\n### Base Elements Props\n\nAll \"DOM\" elements accept a common set of props. All their styles and transformations will\ncascade down the element tree, meaning that when `\u003cg\u003e` has some props applied, those props\nwill also affect its children.\n\nTo summarise these common props affect the current element and its children, but never\nsibling elements or parents.\n\n| Prop            | Type         | Description                                   |\n| --------------- | ------------ | --------------------------------------------- |\n| `fill`          | `colorT`     | Changes the current fill colour               |\n| `stroke`        | `colorT`     | Changes the current stroke colour             |\n| `strokeWidth`   | `int`        | Changes the current stroke width              |\n| `strokeLinecap` | `strokeCapT` | Changes the current stroke cap style          |\n| `x`             | `float`      | Moves the drawing starting point horizontally |\n| `y`             | `float`      | Moves the drawing starting point vertically   |\n| `rotate`        | `float`      | Rotates the canvas                            |\n| `scaleX`        | `float`      | Scales the canvas horizontally                |\n| `scaleY`        | `float`      | Scales the canvas vertically                  |\n| `shearX`        | `float`      | Shears the canvas horizontally                |\n| `shearY`        | `float`      | Shears the canvas vertically                  |\n\n### Elements\n\nThese are all \"DOM\" elements that can be drawn. Remember that all of the above\nbase props apply to all of these elements.\n\nAll of these element's props are technically inside a single shared type, due to\nReasonReact's transpilation limitations. They're also all optional, but mostly\nnot passing a required prop will default to an appropriate value.\n\n#### `\u003cg\u003e`\n\nThe `g` (\"group\") element accepts all base props but doesn't draw or render anything\non its own. It also accepts any number of children, while all other elements don't\naccept any children.\n\nIf you don't need to pass any prop to `\u003cg\u003e` you can also just use a fragment (`\u003c\u003e`).\n\n#### `\u003crect\u003e`\n\nDraws a rectangle.\n\n| Prop     | Type    | Description            |\n| -------- | ------- | ---------------------- |\n| `width`  | `float` | The rectangle's width  |\n| `height` | `float` | The rectangle's height |\n\n#### `\u003cline\u003e`\n\nDraws a line.\n\n\u003e Note: The points still stay relevant to the current coordinate system's translation.\n\u003e So keep in mind that `x` and `y` will still apply to this element and will move it.\n\n| Prop | Type    | Description                        |\n| ---- | ------- | ---------------------------------- |\n| `x1` | `float` | Starting point's `x` coordinate    |\n| `y1` | `float` | Starting point's `y` coordinate    |\n| `x2` | `float` | Destination point's `x` coordinate |\n| `y2` | `float` | Destination point's `y` coordinate |\n\n#### `\u003ctriangle\u003e`\n\nDraws a triangle.\n\n\u003e Note: The points still stay relevant to the current coordinate system's translation.\n\u003e So keep in mind that `x` and `y` will still apply to this element and will move it.\n\n| Prop | Type    | Description                 |\n| ---- | ------- | --------------------------- |\n| `x1` | `float` | 1st corner's `x` coordinate |\n| `y1` | `float` | 1st corner's `y` coordinate |\n| `x2` | `float` | 2nd corner's `x` coordinate |\n| `y2` | `float` | 2nd corner's `y` coordinate |\n| `x3` | `float` | 3rd corner's `x` coordinate |\n| `y3` | `float` | 3rd corner's `y` coordinate |\n\n#### `\u003cellipse\u003e`\n\nDraws an ellipse.\n\n\u003e Note: The points still stay relevant to the current coordinate system's translation.\n\u003e So keep in mind that `x` and `y` will still apply to this element and will move it.\n\n| Prop | Type    | Description                         |\n| ---- | ------- | ----------------------------------- |\n| `cx` | `float` | The ellipse's center `x` coordinate |\n| `cy` | `float` | The ellipse's center `y` coordinate |\n| `rx` | `float` | The ellipse's horizontal radius     |\n| `ry` | `float` | The ellipse's vertical radius       |\n\n#### `\u003ccircle\u003e`\n\nDraws a circle.\n\n\u003e Note: The points still stay relevant to the current coordinate system's translation.\n\u003e So keep in mind that `x` and `y` will still apply to this element and will move it.\n\n| Prop | Type    | Description                         |\n| ---- | ------- | ----------------------------------- |\n| `cx` | `float` | The circle's center `x` coordinate |\n| `cy` | `float` | The circle's center `y` coordinate |\n| `r` | `float` | The circle's radius     |\n\n#### `\u003ctext\u003e`\n\nDraws text. Note that it's not its children that's of type `string`,\nbut instead it accepts a prop.\n\nMore on how to _load_ fonts in the\n[Reprocessing docs](https://schmavery.github.io/reprocessing/api/Reprocessing_Draw.html#value-loadFont).\n\n| Prop   | Type     | Description                   |\n| ------ | -------- | ----------------------------- |\n| `font` | `fontT`  | Font used to draw the text    |\n| `body` | `string` | The string that will be drawn |\n\n#### `\u003cimage\u003e`\n\nDraws an image. Like in Reprocessing itself, if `width` and\n`height` are not passed, then the image's resolution is used.\n\nMore on how to _load_ images in the\n[Reprocessing docs](https://schmavery.github.io/reprocessing/api/Reprocessing_Draw.html#value-loadImage).\n\n| Prop     | Type     | Description                            |\n| -------- | -------- | -------------------------------------- |\n| `image`  | `imageT` | The image that should be drawn         |\n| `width`  | `float`  | The width at which the image is drawn  |\n| `height` | `float`  | The height at which the image is drawn |\n\n### Component Creation\n\nWhen you create a component there's two helpers you can use:\n\n- statelessComponent\n- reducerComponent\n\nBoth accept a `string` as an argument, which should be your _unique_ component\nname. An exception will be raised if the name is already in use.\n\nThe return value of these functions are component templates, which you can then\nspread into your `make`'s return value.\n\n### Component \u0026 Element Types\n\n#### `selfT`\n\nMultiple component lifecycle methods receive `self` as an argument. The type\nof this argument is `selfT`. This is pretty similar (but more simplistic) to\n`ReasonReact`'s `self` argument.\n\n```reason\ntype selfT('state, 'action) = {\n  send: 'action =\u003e unit,\n  state: 'state,\n  glEnv: Reprocessing.glEnvT\n};\n```\n\n`send` is used to dispatch an action to the component's `reducer`, which will\nbe queued up and run before the next rerender.\n\n`state` is the current state of the mounted component.\n\n`glEnv` is just [Reprocessing's GL Env](https://github.com/Schmavery/reprocessing/blob/master/src/Reprocessing_Env.rei).\n\n#### `componentT`\n\nThis is the type of every component.\n\n```reason\ntype componentSpecT('state, 'action, 'initState) = {\n  internal: internalT('state, 'action),\n  initialState: Reprocessing.glEnvT =\u003e 'initState,\n  willUpdate: selfT('state, 'action) =\u003e 'state,\n  render: selfT('state, 'action) =\u003e elementT,\n  didMount: selfT('state, 'action) =\u003e unit,\n  didUpdate: selfT('state, 'action) =\u003e unit,\n  willUnmount: selfT('state, 'action) =\u003e unit,\n  reducer: ('action, 'state) =\u003e 'state\n}\n```\n\n### Component API\n\n#### `initialState`\n\n\u003e `Reprocessing.glEnvT =\u003e 'initState`\n\nThis method receives the [Reprocessing GL Env](https://github.com/Schmavery/reprocessing/blob/master/src/Reprocessing_Env.rei) and should return the initial state of the component.\n\nIt's only called before the component is mounted as an element.\n\n#### `willUpdate`\n\n\u003e `selfT =\u003e 'state`\n\nThis method is called before a (mounted) component is rerendered / redrawn.\nIt can return new state that will be used during rendering.\n\n#### `render`\n\n\u003e `selfT =\u003e elementT`\n\nThe well known `render` method returns new elements.\nYou can use `ReasonReact.null` if you don't wish to return and render\nany children. When you want to render multiple elements without having a wrapper\nelement drawn, use fragments.\n\nMore information can be found in [ReasonReact's JSX docs](https://reasonml.github.io/reason-react/docs/en/jsx)\nwhich all apply to Moomin as well.\n\n#### `didMount`\n\n\u003e `selfT =\u003e unit`\n\nThis method is called after `render` when the component has been mounted for the first time.\n\n#### `didUpdate`\n\n\u003e `selfT =\u003e unit`\n\nThis method is called after `render` when the (mounted) component has just rerendered.\n\n#### `willUnmount`\n\n\u003e `selfT =\u003e unit`\n\nThis method is called when a component is unmounting.\n\n#### `reducer`\n\n\u003e `('action, 'state) =\u003e 'state`\n\nUnlike in `ReasonReact` the reducer methods in Moomin just return a new version of the state given\nan action. This is because there's no need to avoid rerenders since they're happening at 60 FPS anyway.\n\n### Standard Components\n\nMoomin exposes an `Std` module with some \"standard\" utility components. Currently there's only one.\n\n#### `\u003cStd.Setup\u003e`\n\nThis component should be used under all circumstances as it sets up the window / canvas size and\nredraws the background colour.\n\nIt should wrap all your other elements. See the example for more information\n\n| Prop         | Type              | Description                                         |\n| ------------ | ----------------- | --------------------------------------------------- |\n| `width`      | `int`             | The desired window / canvas width                   |\n| `height`     | `int`             | The desired window / canvas height                  |\n| `background` | `colorT`          | The canvas' background colour                       |\n| `children`   | `array(elementT)` | Child elements (note that it accepts multiple ones) |\n\n### Reprocessing APIs\n\nReprocessing's modules are exposed in the same way as they would be if you'd `open Reprocessing`.\n\nYou will find:\n\n- `Utils`\n- `Constants`\n- `Draw`\n- `Env`\n- `Events`\n\nApart from those Moomin comes with `Colors` as well, which adds some of its base colours and might\nhave more utilities in the future.\n\n## LICENSE\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkitten%2Fmoomin","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkitten%2Fmoomin","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkitten%2Fmoomin/lists"}