41 releases
0.2.0-beta.10 | Oct 30, 2024 |
---|---|
0.2.0-beta.6 | Sep 3, 2024 |
0.2.0-beta.5 | Jul 8, 2024 |
0.2.0-beta.2 | Mar 25, 2024 |
0.1.17 | Oct 14, 2022 |
#82 in Game dev
396 downloads per month
90KB
2K
SLoC
devotee
Simplistic visualization project.
Using devotee
Creating an app
Devotee app is represented by the Root
implementation and a Backend
system.
Root implementation
Root
implementor must choose desired input system, pixel data converter, and render surface.
Also, it has to implement update
, render
and converter
methods.
Minimalist Root
implementation may look like this:
struct Minimal;
impl Root for Minimal {
type Input = NoInput;
type Converter = BlackWhiteConverter;
type RenderSurface = Canvas<bool>;
fn update(&mut self, _: AppContext<Self::Input>) {}
fn render(&self, _: &mut Self::RenderSurface) {}
fn converter(&self) -> Self::Converter {
BlackWhiteConverter
}
}
This Root
implementation:
- Uses
NoInput
as input system; - Relies on the
BlackWhiteConverter
(implementation will be discussed later) to convert data of theRenderSurface
; - Uses
Canvas
withbool
pixels as aRenderSurface
; - Does nothing during
update
; - Draws nothing during
render
; - Returns
BlackWhiteConverter
instance for data conversion;
The sample BlackWhiteConverter
is implemented as:
struct BlackWhiteConverter;
impl Converter for BlackWhiteConverter {
type Data = bool;
fn convert(&self, _x: usize, _y: usize, data: Self::Data) -> u32 {
if data {0xffffffff} else {0xff000000}
}
}
It ignores x
and y
coordinates of the pixel and returns either pure white or pure black depending on the data
value.
Backend usage
So, with the Root
being implemented it is time to launch it using some backend.
For this example we will rely on the Softbuffer-based backend implementation.
fn main() -> Result<(), Error> {
let backend = SoftBackend::try_new("minimal")?;
backend.run(
App::new(Minimal),
SoftMiddleware::new(Canvas::with_resolution(false, 128, 128), NoInput),
Duration::from_secs_f32(1.0 / 60.0),
)
}
Updating app state
Consider Extended
implementation of Root
.
struct Extended {
counter: f32,
}
Let it use Keyboard
as input.
It shuts down on the Escape
button being pressed.
Also, it counts passed simulation time in counter
.
So, first part of its implementation looks like this:
impl Root for Extended {
type Input = Keyboard;
type Converter = BlackWhiteConverter;
type RenderSurface = Canvas<bool>;
fn update(&mut self, mut context: devotee::app::AppContext<Self::Input>) {
if context.input().just_pressed(KeyCode::Escape) {
context.shutdown();
}
self.counter += context.delta().as_secs_f32();
}
// ...
}
During render it cleans render surface, calculates the surface center and draws two filled circles using painter
.
Painter
instance accepts functions as arguments instead of pure colors.
The function decides what to do with the pixel passed given its coordinates.
paint
is a predefined function to override any original value.
Note that there are two implementations of painter
: for i32
coordinates and (subpixel one) for f32
coordinates.
//. ..
fn render(&self, surface: &mut Self::RenderSurface) {
surface.clear(false);
let center = surface.dimensions().map(|a| a as f32) / 2.0;
let mut painter = surface.painter();
let radius = 48.0 + 16.0 * self.counter.sin();
painter.circle_f(center, radius, paint(true));
painter.circle_f(center, radius / 2.0, |x, y, _| (x + y) % 2 == 0)
}
// ...
Examples
There are some examples in the examples
folder.
License
devotee
is licensed under the MIT
license.
Dependencies
~0–34MB
~540K SLoC