Custom Swing layout - java

I'm creating a game and to set up the frame the way I want it, I need to some how create a costume layout. I have tried creating multiple panels with different layouts such as Flow and Border and adding panels on the main panel but I still don't get the desired outcome. What I desire is shown in the attached picture. Please tell me if any code segments would help.
Questions:
1. Is it possible to get such layout? (I want it to be fixed.)
Under the "button" there is the JLabel you see in the picture. I want its text to start from left, go down the column, and start on the middle, and then to the right column. Is that possible?

There is a slight variation, as to what I wrote in comments, since BoxLayout is somethingy, that I never use, since I am not good in it :-)
Though what I did to overcome, this shortcoming of mine, is to take 2 JPanels, one each for JTextField and JButton and the rest is the same, as shown in this example:
import java.awt.*;
import javax.swing.*;
public class LayoutExample {
private static final int GAP = 5;
private static final int TOTAL_LABELS = 18;
private JTextField tField;
private JButton button;
private JLabel[] labels;
private void displayGUI () {
JFrame frame = new JFrame ( "Layout Example" );
frame.setDefaultCloseOperation ( JFrame.DISPOSE_ON_CLOSE );
JPanel contentPane = getPanel ();
contentPane.setLayout ( new BorderLayout ( GAP, GAP ) );
JPanel headerPanel = getPanel ();
headerPanel.setLayout ( new GridLayout ( 0, 1, GAP, GAP ) );
JPanel textFieldPanel = getPanel ();
tField = new JTextField ( 10 );
textFieldPanel.add ( tField );
JPanel buttonPanel = getPanel ();
button = new JButton ( "Button" );
buttonPanel.add ( button );
headerPanel.add ( textFieldPanel );
headerPanel.add ( buttonPanel );
contentPane.add (headerPanel, BorderLayout.PAGE_START );
JPanel centerPanel = getPanel ();
centerPanel.setLayout ( new GridLayout ( 0, 3, GAP, GAP ) );
labels = new JLabel [ TOTAL_LABELS ];
for ( int i = 0; i < labels.length; ++i ) {
labels [ i ] = new JLabel ( String.valueOf ( i ), JLabel.CENTER );
centerPanel.add ( labels [ i ] );
}
contentPane.add ( centerPanel, BorderLayout.CENTER );
frame.setContentPane ( contentPane );
frame.pack ();
frame.setLocationByPlatform ( true );
frame.setVisible ( true );
}
private JPanel getPanel () {
JPanel panel = new JPanel ();
panel.setOpaque ( true );
panel.setBorder ( BorderFactory.createEmptyBorder ( GAP, GAP, GAP, GAP ) );
return panel;
}
public static void main ( String[] args ) {
Runnable runnable = new Runnable () {
#Override
public void run () {
new LayoutExample ().displayGUI ();
}
};
EventQueue.invokeLater ( runnable );
}
}
OUTPUT:

Related

Java: Allow mouse scrolling over text areas [duplicate]

I have an array of JTextPanes inside JScrollPanes. All those components I mentioned are inside a JPanel inside a JScrollPane.
At the start of the program, none of the JTextPanes have any text, so the scrollbars for them are not visible. The scrollbars for the JPanel are visible, because I have a lot of components in it.
My issue is that if the cursor is over one of the JTextPanes and I try to scroll, nothing happens because the computer thinks I want to scroll with the JTextPane's scrollbars. What I would like to happen is for the computer to realize that I'm trying to scroll with the JPanel's scrollbar. Is there any way I could accomplish this?
Thanks!
Edit:
You could produce a similar UI to the one above with this code ( this is the constructor of a class extending JFrame - apologies for ignoring a lot of good coding habits ):
public JFrameTest() {
JPanel panel = new JPanel( new GridLayout( 10 , 10 , 10 , 10 ) );
for ( int i = 0 ; i < 10 ; i ++ ) {
for ( int j = 0 ; j < 10 ; j ++ ) {
JScrollPane paneToAdd = new JScrollPane( new JTextPane() ) {
/**
*
*/
private static final long serialVersionUID = 1L;
#Override
public Dimension getPreferredSize() {
return new Dimension( 100 , 100 );
}
};
panel.add( paneToAdd );
}
}
add( new JScrollPane( panel ) );
setSize( 700 , 500 );
setVisible( true );
setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
}
The following code was taken as a quick hack from Mouse Wheel Controller.
Basically it intercepts the MouseWheelEvent for the scroll pane containing the text area. If the scrollbar is visible it redispatches the event back to the same scroll pane otherwise it finds the parent scrollPane and dispatches the event to that scrollPane.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class MouseWheelToParent implements MouseWheelListener
{
private JScrollPane scrollPane;
private MouseWheelListener[] realListeners;
public MouseWheelToParent(JScrollPane scrollPane)
{
this.scrollPane = scrollPane;
install();
}
public void install()
{
if (realListeners != null) return;
// Keep track of original listeners so we can use them to
// redispatch an altered MouseWheelEvent
realListeners = scrollPane.getMouseWheelListeners();
for (MouseWheelListener mwl : realListeners)
{
scrollPane.removeMouseWheelListener(mwl);
}
// Intercept events so they can be redispatched
scrollPane.addMouseWheelListener(this);
}
/**
* Remove the class as the default listener and reinstall the original
* listeners.
*/
public void uninstall()
{
if (realListeners == null) return;
// Remove this class as the default listener
scrollPane.removeMouseWheelListener( this );
// Install the default listeners
for (MouseWheelListener mwl : realListeners)
{
scrollPane.addMouseWheelListener( mwl );
}
realListeners = null;
}
// Implement MouseWheelListener interface
/**
* Redispatch a MouseWheelEvent to the real MouseWheelListeners
*/
public void mouseWheelMoved(MouseWheelEvent e)
{
// System.out.println(e.getScrollType() + " : " + e.getScrollAmount() + " : " + e.getWheelRotation());
JScrollPane scrollPane = (JScrollPane)e.getComponent();
if (scrollPane.getVerticalScrollBar().isVisible())
{
// Redispatch the event to original MouseWheelListener
for (MouseWheelListener mwl : realListeners)
{
mwl.mouseWheelMoved( e );
}
}
else
{
dispatchToParent(e, scrollPane);
return;
}
}
private void dispatchToParent(MouseWheelEvent e, JScrollPane scrollPane)
{
Component ancestor = SwingUtilities.getAncestorOfClass(JScrollPane.class, scrollPane);
MouseWheelEvent mwe = new MouseWheelEvent(
ancestor,
e.getID(),
e.getWhen(),
e.getModifiersEx(),
e.getX(),
e.getY(),
e.getXOnScreen(),
e.getYOnScreen(),
e.getClickCount(),
e.isPopupTrigger(),
e.getScrollType(),
e.getScrollAmount(),
e.getWheelRotation());
ancestor.dispatchEvent(mwe);
}
private static void createAndShowUI()
{
JPanel panel = new JPanel( new GridBagLayout() );
GridBagConstraints gbc = new GridBagConstraints();
gbc.insets = new Insets(10, 10, 10, 10);
for (int y = 0; y < 10; y++)
{
for (int x = 0; x < 5; x++)
{
gbc.gridx = x;
gbc.gridy = y;
JTextArea textArea = new JTextArea(5, 20);
JScrollPane scrollPane = new JScrollPane( textArea );
scrollPane.setMinimumSize( scrollPane.getPreferredSize() );
new MouseWheelToParent(scrollPane);
panel.add(scrollPane, gbc);
if (x == 0 && y ==0)
{
textArea.append("1\n2\n3\n4\n5\n6\n7\n8\n9");
textArea.setCaretPosition(0);
}
}
}
JFrame frame = new JFrame("TextAreaSSCCE");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add( new JScrollPane( panel ) );
frame.setSize(400, 400);
frame.setLocationByPlatform( true );
frame.setVisible( true );
}
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
public void run()
{
createAndShowUI();
}
});
}
}

Java Layout with Component always in Top Right

The primary GUI of my application is composed of a JDesktopPane at the CENTER of a frame's content pane using a BorderLayout. I am hoping to have a component placed in the top right of the screen that still allows the user to drag JInternalFrames within the space to the left and and bottom of this component.
Setting the component to the NORTH or EAST of the BorderLayout seems to fill the entire space. I am thinking BorderLayout may not be the best layout manager for what I am trying to accomplish? Any suggestions on a better approach?
Check out the OverlayLayout. It allows you to stack components on top of one another.
You need to manipulate the setAlignmentX(..) and setAlignmentY(...)` methods to get the layout you want. It is not always intuitive how these alignments work together but setting the component to the top/left is relatively easy.
Here is a little demo for you to play with:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.*;
public class OverlayLayoutTest extends JPanel
implements ActionListener
{
JPanel green;
JPanel red;
JLabel greenLabel;
JLabel redLabel;
JComboBox greenAlignmentX;
JComboBox greenAlignmentY;
JComboBox redAlignmentX;
JComboBox redAlignmentY;
public OverlayLayoutTest()
{
setLayout( new BorderLayout(10, 10) );
add(createNorthPanel(), BorderLayout.NORTH);
add(createCenterPanel(), BorderLayout.CENTER);
add(createSouthPanel(), BorderLayout.SOUTH);
}
private JPanel createNorthPanel()
{
JPanel panel = new JPanel();
panel.add( new JLabel("Green:") );
greenLabel = new JLabel();
panel.add( greenLabel );
panel.add( new JLabel("Red:") );
redLabel = new JLabel();
panel.add( redLabel );
return panel;
}
private JPanel createCenterPanel()
{
JPanel panel = new JPanel();
panel.setLayout( new OverlayLayout(panel) );
panel.setBackground( Color.ORANGE );
panel.setPreferredSize( new Dimension(200, 200) );
red = new JPanel();
red.setBackground( Color.RED );
red.setPreferredSize( new Dimension(50, 50) );
red.setMaximumSize( red.getPreferredSize() );
red.setMinimumSize( red.getPreferredSize() );
panel.add( red );
green = new JPanel();
green.setBackground( Color.GREEN );
green.setPreferredSize( new Dimension(100, 100) );
green.setMaximumSize( green.getPreferredSize() );
green.setMinimumSize( green.getPreferredSize() );
panel.add( green );
JPanel wrap = new JPanel();
wrap.add( panel );
return wrap;
}
private JPanel createSouthPanel()
{
JPanel panel = new JPanel( new GridLayout(1, 0, 10, 10) );
JPanel green = new JPanel(new GridLayout(0, 2, 5, 5) );
green.setBorder( new TitledBorder("Green Alignment") );
green.add( new JLabel("X Alignment:") );
greenAlignmentX = createComboBox();
green.add( greenAlignmentX );
green.add( new JLabel("Y Alignment:") );
greenAlignmentY = createComboBox();
green.add( greenAlignmentY );
panel.add( green );
JPanel red = new JPanel(new GridLayout(0, 2, 5, 5) );
red.setBorder( new TitledBorder("Red Alignment") );
red.add( new JLabel("X Alignment:") );
redAlignmentX = createComboBox();
red.add( redAlignmentX );
red.add( new JLabel("Y Alignment:") );
redAlignmentY = createComboBox();
red.add( redAlignmentY );
panel.add( red );
JButton reset = new JButton("Reset Alignment");
reset.addActionListener( this );
panel.add( reset );
return panel;
}
public void actionPerformed(ActionEvent e)
{
green.setAlignmentX( ((Float)greenAlignmentX.getSelectedItem()) );
green.setAlignmentY( ((Float)greenAlignmentY.getSelectedItem()) );
red.setAlignmentX( ((Float)redAlignmentX.getSelectedItem()) );
red.setAlignmentY( ((Float)redAlignmentY.getSelectedItem()) );
JPanel parent = (JPanel)green.getParent();
parent.revalidate();
/*
System.out.print(green.getAlignmentX() + " : ");
System.out.print(green.getAlignmentY() + " : ");
System.out.print(red.getAlignmentX() + " : ");
System.out.print(red.getAlignmentY() + " : ");
System.out.println();
*/
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
greenLabel.setText( green.getLocation().toString() );
redLabel.setText( red.getLocation().toString() );
}
});
}
private JComboBox createComboBox()
{
JComboBox<Float> comboBox = new JComboBox<Float>();
comboBox.addItem( new Float(0f) );
comboBox.addItem( new Float(0.25f) );
comboBox.addItem( new Float(0.5f) );
comboBox.addItem( new Float(0.75f) );
comboBox.addItem( new Float(1.0f) );
comboBox.setSelectedItem(0.5f);
return comboBox;
}
private static void createAndShowUI()
{
JFrame frame = new JFrame("OverlayLayoutTest");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add( new OverlayLayoutTest() );
frame.pack();
frame.setLocationByPlatform( true );
frame.setVisible( true );
}
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
public void run()
{
createAndShowUI();
}
});
}
}
set the alignment X values to 1.0 for both components
set the alignment Y values to 0.0 for both components
and you should get the layout you want.
Edit:
Missed the part about dragging a JInternalFrame. So this imples you are using a JDesktopPane to support the dragging. A JDesktopPane uses a null layout to allow you to drag components around.
There is no reason you can't add another component (other than a JInternalFrame) to the desktop. You just need to set the size/location of this component to be displayed at the top right of the desktop. You would then need to add a ComponentListener to the desktop pane to listen for the componentResized event. When this event is fired you would need to recalucate the location of the component to reset it to the top right.

Java Scrolling with the More Logical ScrollBar

I have an array of JTextPanes inside JScrollPanes. All those components I mentioned are inside a JPanel inside a JScrollPane.
At the start of the program, none of the JTextPanes have any text, so the scrollbars for them are not visible. The scrollbars for the JPanel are visible, because I have a lot of components in it.
My issue is that if the cursor is over one of the JTextPanes and I try to scroll, nothing happens because the computer thinks I want to scroll with the JTextPane's scrollbars. What I would like to happen is for the computer to realize that I'm trying to scroll with the JPanel's scrollbar. Is there any way I could accomplish this?
Thanks!
Edit:
You could produce a similar UI to the one above with this code ( this is the constructor of a class extending JFrame - apologies for ignoring a lot of good coding habits ):
public JFrameTest() {
JPanel panel = new JPanel( new GridLayout( 10 , 10 , 10 , 10 ) );
for ( int i = 0 ; i < 10 ; i ++ ) {
for ( int j = 0 ; j < 10 ; j ++ ) {
JScrollPane paneToAdd = new JScrollPane( new JTextPane() ) {
/**
*
*/
private static final long serialVersionUID = 1L;
#Override
public Dimension getPreferredSize() {
return new Dimension( 100 , 100 );
}
};
panel.add( paneToAdd );
}
}
add( new JScrollPane( panel ) );
setSize( 700 , 500 );
setVisible( true );
setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
}
The following code was taken as a quick hack from Mouse Wheel Controller.
Basically it intercepts the MouseWheelEvent for the scroll pane containing the text area. If the scrollbar is visible it redispatches the event back to the same scroll pane otherwise it finds the parent scrollPane and dispatches the event to that scrollPane.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class MouseWheelToParent implements MouseWheelListener
{
private JScrollPane scrollPane;
private MouseWheelListener[] realListeners;
public MouseWheelToParent(JScrollPane scrollPane)
{
this.scrollPane = scrollPane;
install();
}
public void install()
{
if (realListeners != null) return;
// Keep track of original listeners so we can use them to
// redispatch an altered MouseWheelEvent
realListeners = scrollPane.getMouseWheelListeners();
for (MouseWheelListener mwl : realListeners)
{
scrollPane.removeMouseWheelListener(mwl);
}
// Intercept events so they can be redispatched
scrollPane.addMouseWheelListener(this);
}
/**
* Remove the class as the default listener and reinstall the original
* listeners.
*/
public void uninstall()
{
if (realListeners == null) return;
// Remove this class as the default listener
scrollPane.removeMouseWheelListener( this );
// Install the default listeners
for (MouseWheelListener mwl : realListeners)
{
scrollPane.addMouseWheelListener( mwl );
}
realListeners = null;
}
// Implement MouseWheelListener interface
/**
* Redispatch a MouseWheelEvent to the real MouseWheelListeners
*/
public void mouseWheelMoved(MouseWheelEvent e)
{
// System.out.println(e.getScrollType() + " : " + e.getScrollAmount() + " : " + e.getWheelRotation());
JScrollPane scrollPane = (JScrollPane)e.getComponent();
if (scrollPane.getVerticalScrollBar().isVisible())
{
// Redispatch the event to original MouseWheelListener
for (MouseWheelListener mwl : realListeners)
{
mwl.mouseWheelMoved( e );
}
}
else
{
dispatchToParent(e, scrollPane);
return;
}
}
private void dispatchToParent(MouseWheelEvent e, JScrollPane scrollPane)
{
Component ancestor = SwingUtilities.getAncestorOfClass(JScrollPane.class, scrollPane);
MouseWheelEvent mwe = new MouseWheelEvent(
ancestor,
e.getID(),
e.getWhen(),
e.getModifiersEx(),
e.getX(),
e.getY(),
e.getXOnScreen(),
e.getYOnScreen(),
e.getClickCount(),
e.isPopupTrigger(),
e.getScrollType(),
e.getScrollAmount(),
e.getWheelRotation());
ancestor.dispatchEvent(mwe);
}
private static void createAndShowUI()
{
JPanel panel = new JPanel( new GridBagLayout() );
GridBagConstraints gbc = new GridBagConstraints();
gbc.insets = new Insets(10, 10, 10, 10);
for (int y = 0; y < 10; y++)
{
for (int x = 0; x < 5; x++)
{
gbc.gridx = x;
gbc.gridy = y;
JTextArea textArea = new JTextArea(5, 20);
JScrollPane scrollPane = new JScrollPane( textArea );
scrollPane.setMinimumSize( scrollPane.getPreferredSize() );
new MouseWheelToParent(scrollPane);
panel.add(scrollPane, gbc);
if (x == 0 && y ==0)
{
textArea.append("1\n2\n3\n4\n5\n6\n7\n8\n9");
textArea.setCaretPosition(0);
}
}
}
JFrame frame = new JFrame("TextAreaSSCCE");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add( new JScrollPane( panel ) );
frame.setSize(400, 400);
frame.setLocationByPlatform( true );
frame.setVisible( true );
}
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
public void run()
{
createAndShowUI();
}
});
}
}

Using two JPanels in one JFrame

I'm attempting to create a program that allows the user to click a button to place something in the JPanel and allowing them to move this item around. I have already found a good layout to use to allow the moving components (see this link). However, I'm just curious the best way to create a layout like this? My hope is to have something like this:
How can I accomplish this? Would I want to use two JPanel's or something else?
The main panel (or the window content pane) would have to have a BorderLayout as the layout manager.
Then, the buttons panel would be added to BorderLayout.WEST and the drag panel to BorderLayout.CENTER.
There is a Visual Guide to swing layout managers.
Try to use JSplitPane:
Here is a code example:
class SplitPane extends JFrame {
private JSplitPane splitPaneV;
private JSplitPane splitPaneH;
private JPanel panel1;
private JPanel panel2;
private JPanel panel3;
public SplitPane(){
setTitle( "Split Pane Application" );
setBackground( Color.gray );
JPanel topPanel = new JPanel();
topPanel.setLayout( new BorderLayout() );
getContentPane().add( topPanel );
// Create the panels
createPanel1();
createPanel2();
createPanel3();
// Create a splitter pane
splitPaneV = new JSplitPane( JSplitPane.VERTICAL_SPLIT );
topPanel.add( splitPaneV, BorderLayout.CENTER );
splitPaneH = new JSplitPane( JSplitPane.HORIZONTAL_SPLIT );
splitPaneH.setLeftComponent( panel1 );
splitPaneH.setRightComponent( panel2 );
splitPaneV.setLeftComponent( splitPaneH );
splitPaneV.setRightComponent( panel3 );
}
public void createPanel1(){
panel1 = new JPanel();
panel1.setLayout( new BorderLayout() );
// Add some buttons
panel1.add( new JButton( "North" ), BorderLayout.NORTH );
panel1.add( new JButton( "South" ), BorderLayout.SOUTH );
panel1.add( new JButton( "East" ), BorderLayout.EAST );
panel1.add( new JButton( "West" ), BorderLayout.WEST );
panel1.add( new JButton( "Center" ), BorderLayout.CENTER );
}
public void createPanel2(){
panel2 = new JPanel();
panel2.setLayout( new FlowLayout() );
panel2.add( new JButton( "Button 1" ) );
panel2.add( new JButton( "Button 2" ) );
panel2.add( new JButton( "Button 3" ) );
}
public void createPanel3(){
panel3 = new JPanel();
panel3.setLayout( new BorderLayout() );
panel3.setPreferredSize( new Dimension( 400, 100 ) );
panel3.setMinimumSize( new Dimension( 100, 50 ) );
panel3.add( new JLabel( "Notes:" ), BorderLayout.NORTH );
panel3.add( new JTextArea(), BorderLayout.CENTER );
}
public static void main( String args[] ){
try {
UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel");
} catch (Exception evt) {}
// Create an instance of the test application
SplitPane mainFrame = new SplitPane();
mainFrame.pack();
mainFrame.setVisible( true );
}
}
You can play with splitPaneH.setOneTouchExpandable true/false
You can confugure divider location for both like:
Toolkit tk = Toolkit.getDefaultToolkit();
Dimension d = tk.getScreenSize();
int width = d.width;
int height = d.height;
spane.setDividerLocation((width*3)/4);
spanex.setDividerLocation(width/4);

How to make CardLayout inside JTabbedPane

I am working on a project with JTabbedPane
When I click TAB2, it shows the PANEL 2. Done
PANEL 2 consist of a JButton "Next" that will switch to PANEL 2.1 and JButton "Previous" to
switch it back to PANEL 2 while still in the TAB 2.
So, it is a Card Layout inside Tab2?
Thanks for helping!
-----------------------------
TAB 1 | TAB 2 | TAB 3 |
-----------------------------
-
-
PANEL 2 -
-
-
-
-
"Previous" "NEXT" -
========================== =
You just need to insert a JPanel in the tab and set its layout as CardLayout. So, inside each "card" you will insert a new JPanel. Here is an example:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class Foo extends JFrame {
public Foo() {
setTitle( "Tabs and Cards" );
setSize( 400, 400 );
setDefaultCloseOperation( EXIT_ON_CLOSE );
JTabbedPane tabbedPane = new JTabbedPane();
// needs to be final to be accessed inside the event handlers
final JPanel tab1 = new JPanel();
final JPanel tab2 = new JPanel();
tab2.setLayout( new CardLayout() );
tabbedPane.addTab( "Tab 1", tab1 );
tabbedPane.addTab( "Tab 2", tab2 );
JPanel tab21 = new JPanel();
tab21.add( new JLabel( "2.1" ) );
JPanel tab22 = new JPanel();
tab22.add( new JLabel( "2.2" ) );
JPanel tab23 = new JPanel();
tab23.add( new JLabel( "2.3" ) );
tab2.add( tab21 );
tab2.add( tab22 );
tab2.add( tab23 );
JButton btnToTab22 = new JButton( "Next!" );
btnToTab22.addActionListener( new ActionListener(){
#Override
public void actionPerformed( ActionEvent evt ) {
// gets the layout, casts it and call next to go to the next card
( ( CardLayout ) tab2.getLayout() ).next( tab2 );
}
});
tab21.add( btnToTab22 );
JButton btnToTab23 = new JButton( "Next!" );
btnToTab23.addActionListener( new ActionListener(){
#Override
public void actionPerformed( ActionEvent evt ) {
( ( CardLayout ) tab2.getLayout() ).next( tab2 );
}
});
tab22.add( btnToTab23 );
add( tabbedPane, BorderLayout.CENTER );
setVisible( true );
}
public static void main( String[] args ) {
new Foo();
}
}
I didn't commented the code because is not so complicated, but I think that you will understand easily. Take a look at the documentation.

Categories