If you’re watching the development of The Honor Sagas, you’ll have heard me talk about a legion-driven 2d Canvas. This has been in Kludgine for a little while, and as I was using that Entity Component System (ECS) to build a systems-oriented game design, I started connecting the dots of what I wanted to do with Kludgine as I started imagining how to solve those problems if my data-store was an ECS. I had previously stopped my Theme development because I wasn’t sure how to use the Type system to encapsulate my problem.
To summarize the problem I was facing with Theming, I recognized that I wanted to be able to have multiple levels of overrides of colors. Let’s imagine the Button. The text color, background color, border color, any shading, and more could all be driven by a theme. I don’t want you to need to initialize every button with every style to make it render – instead, you should be able to have a global Theme that informs these components of how to style themselves.
I knew a straightforward way to handle all of the information I needed with the components built into Kludgine, but how would third party components be able to embrace and extend the existing style information?
I’ve introduced two mechanisms to solve these problems
Entities can have multiple components now
The first change is mostly transparent, but the power it enables will be something that I will leverage as I come back to the UI layer soon to flush out more controls. The entity creation process now allows you to attach extra arbitrary information to an entity, and the entity can query for other components that have been attached to itself.
Under the hood, bounds
, stylesheet
, and the component itself are all stored in a component map. When you ask for those pieces of information, it will be accessed through this map under the hood. Bounds is actually a perfect example of why this refactor will enable better control: What does AbsoluteBounds
mean when you set it on a component that is within a Grid View? Wouldn’t it be better if components that you insert into a GridView you would assign something like a GridLayout instead? This component-based system allows for components to offer flexibility in either checking for multiple types of components, or for specialization, or for many other purposes I haven’t even considered yet! That’s what I love about general-purpose designs is that they open up a lot of powerful potential.
Styles are now component-based
I did a major refactor of the Style API over the last two days. The first refactor was to make it so that each piece of style information was a component. Here’s a before example:
.style(Style {
color: Some(Color::new(1.0, 1.0, 1.0, 0.1)),
background_color: Some(Color::new(1.0, 0.0, 1.0, 0.5)),
font_size: Some(Points::new(72.)),
alignment: Some(Alignment::Right),
..Default::default()
})
And the after:
.style(
Style::new()
.with(ForegroundColor(Color::new(1.0, 1.0, 1.0, 0.1)))
.with(BackgroundColor(Color::new(1.0, 0.0, 1.0, 0.5)))
.with(FontSize::new(72.))
.with(Alignment::Right),
)
I think the results of the API cleanliness are very obvious. But, since these are no longer actual structure fields, accessing is now done using a generic API:
Old:
context.effective_style().alignment
New:
context.effective_style().get::<Alignment>() // or get_or_default::<T>()
This again offers ultimate flexibility towards designing a control. Revisiting the Button example, one possible solution would be to offer a ButtonTextColor
component. The Button would check for that component first, then fall back to ForegroundColor
. Themes would then be able to override ButtonTextColor independently of ForegroundColor. I haven’t begun that refactoring yet, it’s currently just a design that exists in my mind.
Scaling and Styles
The final detail that I like about this design is that there are no longer separate Style
and EffectiveStyle
types. Instead, both Style and EffectiveStyle have been merged into Style, but now each StyleComponent must implement a trait determining how to scale the measurements it contains. There’s a one-liner to skip scaling if the component doesn’t have anything that needs to be scaled.
What’s next?
I’m wanting to switch back to getting my game server deployed for The Honor Sagas. I’ve been noodling over the game server architecture, and I think I have an approach I’m ready to take. This was a fun diversion while I figured out what I wanted to do on that front. I’m expecting very soon to come back and revisit writing a grid-based layout mechanism and start implementing a text edit control, as those will be important in the game quickly.
I was excited enough by these new API changes that I wanted to write a little about them, and I figured that would be a good excuse to link this technical explanation from a dev log over on The Honors Saga’s itch.io page.