-
Notifications
You must be signed in to change notification settings - Fork 2
Core Topic – Customising Your View's Appearance
Views are one of the most important and frequently used class in Silica. Anything from a Button to a Window is a View.
These are all the View subclasses that come with Silica. The tree represents their class hierarchy (i.e., what class they extend from).
- Button
- Checkbox
- Container
- Label
- ListItem
- MaskedTextBox
- MenuBarItem
- MenuButton
- MenuItem
- ProgressBar
- Radio
- Scrollbar
- SeparatorMenuItem
- SeparatorView
- TextBox
- ToolbarDynamicSpace
- ToolbarStaticSpace
To achieve the interface you want, it may be necessary to create your own view. If it looks like one of the default views won't do what you want, chances are you need to create your own.
Creating custom views is an advanced topic. If you're not comfortable with the basics of Silica you might want to learn a bit more first. Knowing how Silica works is vital to creating custom views.
Before you begin you should have a good understanding of the following topics:
To get started you will need to create a View subclass. As the Style Guide notes, if it assists with conveying what the your View subclass is, append 'View' to the end of your class' name. This isn't overly common for View subclasses normally (except for `Container subclasses)
class "CustomView" extends "View" {
}
At this point you should be able to add CustomView to an interface file without errors appearing. It will be invisible at the moment, that is normal. Read the Changing a View's Appearance section for how to change this.
A View subclass' appearance determines what it looks like.
View subclasses draw to the screen by adding GraphicsObject instances to the View's Canvas. A Canvas is simply a GraphicsObject that can hold child GraphicsObject instances within itself.
The main point to know is that each View has a canvas, accessed by self.canvas
, and if you want to draw something you add a GraphicsObject subclass to that canvas.
Drawing is a very detailed topic, if you want to read more about it, look at the Drawing page.
As you are defining what a View looks like you will need to set properties in a theme. If you aren't already using a theme file in your application, create a blank one now, make your interface use it and add a node for your View subclass.
<Theme extends="default">
<CustomView>
</CustomView>
</Theme>
The initial setup of your Canvas' GraphicsObject instances must be done within :initialiseCanvas. You must also call :initialiseCanvas on super, if you do not your view will not have a Canvas, meaning no drawing!
function CustomView:initialiseCanvas()
self.super:initialiseCanvas()
-- your code here
end
A blank View subclass will be invisible at this stage. To give it a fill (background) colour we change the fillColour
property of the View's Canvas.
Create a fillColour
property in your View's theme node.
<CustomView>
<fillColour type="Graphics.colours" default="RED"/>
</CustomView>
Once you have defined a property in your theme file you then need to link it.
Under the super call, make the connection.
function CustomView:initialiseCanvas()
self.super:initialiseCanvas()
-- canvas property | theme property
-- \/ \/
self.theme:connect( self.canvas, "fillColour", "fillColour" )
end
What this is doing is linking the fillColour
property you defined in your theme (the last argument) to the fillColour
property of self.canvas
. If the theme changes or style changes this will automatically be updated.
Because the theme property and Canvas property have the same name, you can omit the last argument, it will assume the same name. Hence, the below will result in the same as the code above.
self.theme:connect( self.canvas, "fillColour" )
You should now see your view as a plain red colour rectangle. If you can't see it make sure you've set the width and height large enough to see it.
Chances are most of your View subclasses won't be plain coloured rectangles. Let's make it a more interesting shape, a rounded rectangle.
Silica comes with a wide variety of different shaped GraphicsObject subclasses you can use, as well as the Path object, allowing for linear, bezier and circular curves. You can read about the different subclasses of GraphicsObject on the Drawing page as well as how to make your own. This section will focus on how to use GraphicsObject subclasses with custom views, rather than on the GraphicsObject subclasses.
Rounded rectangles allow you to choose their corner radius, to do this you will need to create another theme property.
Add a cornerRadius property to you theme node. Unlike previously, this time we need to use the number type, rather than the Graphics.colours
type as the radius is a number.
<cornerRadius type="number" default="6"/>
If you still have the code connecting self.canvas
to fillColour
, remove that now.
First we need to create our rounded rectangle and insert it in to our canvas. Insert this code after the super call.
local roundedRectangleObject = self.canvas:insert( RoundedRectangle( x, y, width, height ) )
Replace the x
, y
, width
and height
properties. With the values you want. To make it cover your entire view, you would use the following. All coordinates are relative to the View, so a position 1, 1 will be in the top left corner. (Remember: self is referring to the View)
RoundedRectangle( 1, 1, self.width, self.height )
Calling RoundedRectangle()
creates an instance of a RoundedRectangle, a subclass of GraphicsObject. self.canvas:insert
then inserts that RoundedRectangle in to the canvas. The :insert
function returns the RoundedRectangle instance.
Once you have inserted the GraphicsObject you need to link it to its theme properties. The properties of RoundedRectangle we want to link are fillColour
and radius
.
Let's do that now. Add this after inserting the RoundedRectangle.
local theme = self.theme
theme:connect( roundedRectangleObject, "fillColour" )
theme:connect( roundedRectangleObject, "radius", "cornerRadius" )
You'll notice that we are using cornerRadius for the theme property, but
radius
for the RoundedRectangle property. Using cornerRadius is just personal preference to distinguish which radius it is when there are multiple rounded rectangles.
If you run your program now you'll probably see something; but probably not what you want. You'd expect your rounded rectangle to cover the entire view!
This isn’t happening because after you insert the RoundedRectangle, the width of the View is changed to the width you set in the interface. This is because the properties you assigned in your interface file are actually set after the canvas is initialised (don't worry, there's good reasoning behind this, but it is annoying).
If you View changes size later your RoundedRectangle will stay it's original size (which is probably 1 x 1). So how do we fix this?
To solve the issue encountered in previous section we need to change the size of the RoundedRectangle whenever the size of the View changes.
Fortunately Silica has a very way to solve this. However, you first need to assign your RoundedRectangle to a property so you can access it in other functions.
First, define it in the properties table.
class "CustomView" extends "View" {
roundedRectangleObject = false;
}
Then assign the RoundedRectangle instance you created to the property in the :initialiseCanvas
function.
roundedRectangleObject = roundedRectangleObject
When x
, y, width
or height
of your View changes it will call :updateWidth (or the equivalent depending upon what property changed).
As your RoundedRectangle only need to update its size, add the :updateWidth and :updateWidth functions.
function CustomView:updateWidth( width )
self.roundedRectangle.width = width
end
function CustomView:updateHeight( height )
self.roundedRectangle.height = height
end
You can, of course, do whatever you like with the value it gives you (such as divide it by two to make it only cover half the view).
If you run your program again you should see your RoundedRectangle in all its glory! Whenever your View resizes the RoundedRectangle will always cover it.
You should now have a good understanding of to how to:
- Insert GraphicsObject subclasses to a custom View
- Theme GraphicsObject subclasses you insert
- Update the size of GraphicsObject subclasses you insert as the View resizes
What you should try now:
- Look at the Drawing page at the other shapes you can use, such as Circle, and try to achieve the same result.
- Read the next section, which explains how to assign non-theme based values to GraphicsObject subclasses, such as the Text object.
Text is added to a View using a GraphicsObject subclass called Text. The process is almost identical to the process used in the Adding Basics Shapes to a View section. The main difference is you have to set the text of the Text object and update it when the changes.
Follow the same process that was used to create a RoundedRectangle, but instead of RoundedRectangle use a Text object. You don't need to connect your theme to the cornerRadius
and fillColour
properties, but you will need connect to the textColour
property. You will need to keep the :updateWidth and :updateHeight functions because the Text object will clip its contents (i.e., the text) if not large enough.
If you run your program it should open error free, but nothing will be visible. This is because you haven't actually set the text of the Text object yet.
If something isn't working, try to figure out what's wrong based on the error and look back over the previous instructions and ensure that you followed the RoundedRectangle instructions correctly. If you're still lost Get Help.
Give your View subclass a new property called text
. This property will work just like it does in View subclasses like Button and Label. When you set the text
property, either in code or using an interface file, it will automatically change the text that is shown.
If you want your View subclass to have a default value set the property value in the properties table to that value. In this case, the default will be "Hello".
class "CustomView" extends "View" {
textObject = false;
text = "Hello";
}
Now that there is a property called text
you can create a setter which will call code whenever the text
property is changed, including when the View is loaded from an interface file.
Simply set the text
property of the Text object to the new value of the text
property.
function CustomView:setText( text )
self.text = text
self.textObject.text = text
end
Reopen your program and voilà! You should see your text appear. If you set the text
property of your an instance of your subclass the text should automatically change.
Note If the text is cut off or you can't see it there's a strong chance your view is too small or the Text object is to small. If you want your View to automatically adjust its size based on the size of the text look at the Advance Concept – Autosizing page.