morthics
v0.2.4
Published
Mothics is a typescript framework designed to work with WebGL
Maintainers
Readme
Morthics
Morthics is a typescript framework designed to work with WebGL. The framework is based on a component-based approach that makes development faster and easier. Morthics makes it possible to manage the program at a low level, and also ensures high speed of the program due to its simplicity, webgl and many optimizations
Installation
npm i morthicsUsage
main.ts
import { Application } from 'morthics'
;(async () => {
const app = new Application(
{ resizeTo: window, backgroundColor: '#242424' },
Main
)
await app.load
document.body.appendChild(app.canvas)
})()page.ts
import {
Child,
Component,
Container,
PageComponent,
Preloads,
Text,
} from 'morthics'
import styles from './page.style'
export class Main extends PageComponent {
preLoad(): Preloads {
{
return {
googleFonts: ['Snippet'],
}
}
}
render() {
return Container({}, new HelloWorld())
}
}HelloWorld.ts
export class HelloWorld extends Component {
render() {
return Container(
{},
Text(styles.HelloWorld, 'Hello World!'),
)
}
}page.style.ts
export default {
HelloWorld: {
style: {
fill: '#ffffff',
fontSize: 36,
fontFamily: 'Snippet',
},
},
}Documentation
Application
To create an application on Morthics, the first thing to do is create an object of the Application class
import { Application } from 'morthics'
const app = new Application(
{ resizeTo: window, backgroundColor: '#242424' },
Main
)In the constructor of the Application class, the first argument is passed to the object with the application settings, and the second argument is passed to PageComponent
Now we can add our application to the page, but before that we should make sure that the application has time to load and for this purpose the app object has a load field. Then all we need to do is to add the canvas to the desired part of the page.
import { Application } from 'morthics'
;(async () => {
const app = new Application(
{ resizeTo: window, backgroundColor: '#242424' },
Main
)
await app.load
document.body.appendChild(app.canvas)
})()PageComponent
PageComponent is a page component that is similar in its capabilities to a regular component.
Like a Component, it has a render method that returns the markup for the whole page
import { PageComponent, Container, Text } from 'morthics'
export class Main extends PageComponent {
render() {
return Container(
{},
Text({}, 'Hello World!'),
)
}
}preLoad
The preLoad method allows you to preload necessary data such as textures and fonts. To do this, return an object with the required fields from the preLoad method
It is also worth noting that fonts from google fonts and other fonts are downloaded differently in the array googleFonts should be passed to the names of fonts, and in the array fonts links to download them
export class Main extends PageComponent {
preLoad(): Preloads {
{
return {
googleFonts: ['Montserrat', 'Snippet'],
textures: ['https://pixijs.com/assets/bunny.png'],
}
}
}
render() {
return Container({}, new HelloWorld())
}
}Component
Сomponents in Morthics are created based on classes. Each component has its own life cycle and state. On different parts of the lifecycle you can hang event listeners, and the state can be changed, which will lead to the re-rendering of the component
This is an example of a simple counter, its logic can be simplified and not to create unnecessary components, but for a better understanding of working with components, props and state the button was placed in a separate component
page.ts
export class Main extends PageComponent {
preLoad(): Preloads {
{
return {
googleFonts: ['Montserrat', 'Snippet'],
textures: ['https://pixijs.com/assets/bunny.png'],
}
}
}
render() {
return Container({}, new HelloWorld())
}
}Hi.ts
export class Hi extends Component {
beforeRender(): void {
this.state.count = 0
this.ref.Button = new CounterButton()
}
render() {
const { Button } = this.ref
return Container(
{ x: 10 },
Text(
{
style: {
fill: '#fff',
},
},
this.state.count
),
withProps(Button, {
count: this.state.count,
increment: () => this.state.count++,
})
)
}
}CounterButton.ts
interface Props {
count: number
increment: () => void
}
export class CounterButton extends Component<Props> {
render(): Child {
const { count, increment } = this.props
return Container(
{},
Button(
{
onClick: increment,
y: 20,
},
count
)
)
}
}State
State is the data that tells you which component is which at any given moment. Any component except PageComponent has a state. It can be accessed via this.state and data can be written to it by normal assignment, but if you change the state, the component will be re-drawn
class CounterButton extends Component {
beforeRender(): void {
this.state.count = 0
}
render(): Child {
return Container(
{},
Text(
{
x: 130,
style: {
fill: '#fff',
fontFamily: 'Snippet',
},
},
this.state.count
),
Container(
{ y: 50 },
Button(
{
onClick: () => this.state.count++,
},
'increment'
),
Button(
{
x: 75,
onClick: () => this.state.count--,
},
'decrement'
)
)
)
}
}It is also important to realize that if you change several states in a row or change the same state many times, it does not mean that each assignment will be re-rendered, but only when necessary
this.state.count++
this.state.count++
this.state.y *= 2
// 1 re-renderthis.state.count++
setTimeout(() => {
this.state.count++
}, 1000)
// 2 re-rendersthis.state.count = 0
setTimeout(() => {
this.state.count = 0
}, 1000)
// 0 re-rendersProps
Through props you can pass data to the component from its parent for this you need to use the withProps function. If the props changes, the component will be redrawn. Props can be changed from a child, but then the re-rendering will not happen and unexpected behavior is possible, so it is better not to do this and if you really want to change its value, it is better to create a new variable
If necessary, a function can be passed to withProps as a third parameter to say whether the props have changed and depending on the answer the component will either be redrawn or not
export class Main extends PageComponent {
render() {
return Container(
{},
withProps(new YellowButton(), { text: 'button 1' }),
withProps(
new YellowButton(),
{
text: 'button 2',
options: { x: 100, onClick: () => alert("It's button 2") },
},
(lastProps, newProps) => lastProps.text !== newProps.text
)
)
}
}YellowButton.ts
interface Props {
text: string
options?: ButtonOptions
}
export class YellowButton extends Component<Props> {
render(): Child {
const { text, options = {} } = this.props
return Container(
{},
Button(
{
bgColor: 0xffff00,
textStyle: {
fill: '#fff',
},
...options,
},
text
)
)
}
}ref
Ref is the storage of this component. It performs the same functions as state, but with an important difference, which is that when ref is changed, the component is not re-rendered
Also, all the components you want to use should be written to ref (ref is absent in PageComponent, as it is not necessary to use it there)
class CounterButton extends Component {
beforeRender(): void {
this.ref.MyButton = new CounterButton()
this.ref.text = 'Text'
}
render(): Child {
const { MyButton, text } = this.ref
return Container(
{},
MyButton(
{},
text,
)
)
}
}Sprite
Sprite is the analog of img in html it allows you to add images to a page
class Bunny extends Component {
render(): Child {
return Container(
{},
Sprite(
{ width: 50, height: 100 },
'https://pixijs.com/assets/bunny.png'
)
)
}
}Container
Container allows you to group elements
class Bunnies extends Component {
render(): Child {
return Container(
{ x: 50 },
Sprite(
{ width: 50, height: 100 }, // x == 50
'https://pixijs.com/assets/bunny.png'
),
Sprite(
{ x: 100, width: 50, height: 100 }, // x == 150
'https://pixijs.com/assets/bunny.png'
),
)
}
}Text (beta)
Text - allows you to write text on the page
export class HelloWorld extends Component {
render() {
return Container(
{},
Text(
{
style: {
fill: '#ffffff',
fontSize: 36,
fontFamily: 'Snippet',
},
},
'Hello World!'
)
)
}
}Button (beta)
Button creates a button on the page. Initially it has a standard look similar to the browser button but it can be styled to your needs. You can place onClick, onHover, onUnHover event listeners on the button. You can also change border, button background, text styles, padding and more
class YellowButton extends Component<Props> {
render(): Child {
const { text, options = {} } = this.props
return Container(
{},
Button(
{
bgColor: 0xffff00,
textStyle: {
fill: '#fff',
},
...options,
},
text
)
)
}
}animation (beta)
animation is a parameter that can be used with any standard component, it allows you to describe in a simple format the animation to be set for that component. You need to specify keyframes for certain percentages of animation execution
Parameters related to animation
animationTime: number// Animation execution timeanimationType: 'default' | 'linear'// WithlinearanimationType, animation occurs at the same speed at all stages and this is the fastest type of animation. In turn, withdefaultanimation animation is not at a constant speed (by default, the speed changes according to a sinusoidal graph)animationFunction: [(x: number) => number, number,number]// animationFunction and function value area. animationFunction is a function that increments the speed of animation execution at a particular point in time. It is important that the function must be continuous over the specified definition areastartAnimation: boolean// by default it's true but if false then animation will not be applied
class MegaBunny extends Component {
beforeRender(): void {
this.state.y = 0
this.state.count = 0
this.state.startAnimation = false
}
jump() {
this.state.startAnimation = true
this.state.count++
}
render() {
return Container(
{
x: (globalThis.innerWidth - 200) / 2,
y: (globalThis.innerHeight - 400) / 2,
},
Sprite(
{
width: 100,
height: 100,
animation: {
50: {
y: -150,
},
100: {
y: 0,
},
},
animationTime: 500,
animationType: 'default',
animationFunction: [(x) => Math.cos(x) + 1, 0, Math.PI],
startAnimation: this.state.startAnimation,
},
'https://pixijs.com/assets/bunny.png'
),
Text(
{ style: { fill: 0xffffff }, y: 150, x: 40 },
this.state.count
),
Button(
{
onClick: () => this.jump(),
borderSize: [0],
y: 100,
x: 5,
paddingLeft: 5,
paddingRight: 5,
borderRadius: 10,
},
'jump'
)
)
}
}