auto resizing in cocos2dx - android

I have made android build using cocos2dx for the device having size 480*320 and it works fine but when i put the same build in another android device having size 640*480 the scaling issue occurs....
I am using following code to re-size automatically, but it is not working:
AppDelegate app;
CCEGLView& eglView = CCEGLView::sharedOpenGLView();
eglView.setViewName("Hello Lua");
eglView.setFrameSize(480, 320);
// set the design resolution screen size, if you want to use Design Resoulution scaled to current screen, please uncomment next line.
// eglView.setDesignResolutionSize(480, 320);

First of all, the code you have pasted is from main.cpp file which does not play a part when you compile the app for android or windows phone or ios. That file is for win32 project.
Now,
for automatic scaling of app, the function in AppDelegate.cpp should set design resolution as well as policy. I use ExactFit policy because it stretches the app to fill the whole screen area and it works on Android , Windows, iOS everything (I have developed apps and tested) :
bool AppDelegate::applicationDidFinishLaunching()
{
// initialize director
CCDirector *pDirector = CCDirector::sharedDirector();
pDirector->setOpenGLView(CCEGLView::sharedOpenGLView());
// turn on display FPS
CCEGLView::sharedOpenGLView()->setDesignResolutionSize(1200, 720, kResolutionExactFit);
pDirector->setDisplayStats(false);
pDirector->setAnimationInterval(1.0 / 60);
CCScene *pScene = HelloWorld::scene();
pDirector->runWithScene(pScene);
return true;
}
You should also take a look at this Detailed explanation of multi-resolution support to understand it better.

If you are not conscious about stretched look then do this. It will work on all the devices.
pDirector->setOpenGLView(pEGLView);
CCSize frameSize = pDirector->getOpenGLView()->getFrameSize();
CCLog("%.2f and %.2f is Frame size\n",frameSize.width,frameSize.height);
pEGLView->setDesignResolutionSize(960, 640, kResolutionExactFit);
Note: The above setting is for Landscape, if you need it for portrait just reverse the values like pEGLView->setDesignResolutionSize(640, 960, kResolutionExactFit)
The thing to understand here is that everything you will design on the coordinates of 640X960 and kResolutionExactFit will fix the things as Fit to the Screen. To do this more easily you should use Coocos Builder http://cocosbuilder.com/ and design everything on a custom layout of 640X960
I am recommending double resolution (640X960) because this way Quality won't be reduced and will look crisp on all kind of devices, however the drawback of Stretched Look would be there.
If you want to consider multiple resolutions in multiple way then you have to make multiple graphics and multiple Layouts, again Cocos Builder would be the best way to do this.
i.e for iPhone 5 it would be diff and for iPad and standard 480X320 it would be diff. To do this you can put checks on frame size to select respective View.
CCSize frameSize = pDirector->getOpenGLView()->getFrameSize();

You should experience some cutting at each side. This is done because is how best the compatibility mode match the screen res of 480x320. The others option are leaving some part of the height out or stretch the image (at cost of some performance (?) ).
The first can be easy done one the CCEGLView::create method, you can change the MIN to MAX in the scalefactor formula and its done, but this probably will breake all y position of your sprites :P

bool AppDelegate::applicationDidFinishLaunching()
{
// initialize director
CCDirector* pDirector = CCDirector::sharedDirector();
CCEGLView* pEGLView = CCEGLView::sharedOpenGLView();
pDirector->setOpenGLView(pEGLView);
// Set the design resolution
// iPad size
CCPoint res = CCPoint(768,1024);
pEGLView->setDesignResolutionSize(res.x,
res.y,
kResolutionNoBorder);
.....
}

Check the resolution and make changes for screen size:
(This example is a portrait game)
typedef struct tagResource
{
cocos2d::Size size;
char directory[100];
}Resource;
std::vector<std::string> searchPath;
static Resource designResource = { cocos2d::Size(768, 1024), "ipad"};
static Resource smallResource = { cocos2d::Size(320, 480), "iphone"};
static Resource galax7Resource = { cocos2d::Size(600, 1024), "gt_7p" };
static Resource mediumResource = { cocos2d::Size(768, 1024), "ipad"};
static Resource galax10Resource = { cocos2d::Size(800, 1280), "gt_10p"};
static Resource fullHDResource = { cocos2d::Size(1080, 1920), "fullhd"};
static Resource largeResource = { cocos2d::Size(1536, 2048), "ipadhd"};
auto director = Director::getInstance();
auto glview = director->getOpenGLView();
glview->setDesignResolutionSize(designResource.size.width, designResource.size.height, ResolutionPolicy::EXACT_FIT);
Size frameSize = glview->getFrameSize();
Resource realResource;
// if the frame's height is larger than the height of medium size.
if (frameSize.height > fullHDResource.size.height) {
realResource = largeResource;
CCLOG("largeResource");
} else if (frameSize.height > galax10Resource.size.height) {
realResource = fullHDResource;
CCLOG("fullHDResource");
} else if (frameSize.height > mediumResource.size.height) {
realResource = galax10Resource;
CCLOG("galax10Resource");
} else if (frameSize.height > smallResource.size.height) {
if(frameSize.width > galax7Resource.size.width){
realResource = mediumResource;
CCLOG("mediumResource");
}else {
realResource = galax7Resource;
CCLOG("galax7Resource");
}
} else {
realResource = smallResource;
CCLOG("smallResource");
}
director->setContentScaleFactor(MIN(realResource.size.height/designResource.size.height, realResource.size.width/designResource.size.width));
searchPath.push_back(realResource.directory);
auto fileUtils = FileUtils::getInstance();
fileUtils->setSearchPaths(searchPath);
I'm using cocos2dx V3 but the idea is the same for V2.

I have used Following resolution policy in my 3match game
In AppDelegate.cpp
#define IS_LANDSCAPE
#ifdef IS_LANDSCAPE
static cocos2d::Size designResolutionSize = cocos2d::Size(1136,768);
#else
static cocos2d::Size designResolutionSize = cocos2d::Size(768,1136);
#endif
//==========================
bool AppDelegate::applicationDidFinishLaunching() {
// initialize director
auto director = Director::getInstance();
auto glview = director->getOpenGLView();
if(!glview) {
glview = GLViewImpl::create("My Game");
director->setOpenGLView(glview);
}
// turn on display FPS
director->setDisplayStats(false);
// set FPS. the default value is 1.0/60 if you don't call this
director->setAnimationInterval(1.0 / 60);
// Set the design resolution
glview->setDesignResolutionSize(designResolutionSize.width, designResolutionSize.height, ResolutionPolicy::NO_BORDER);
register_all_packages();
// create a scene. it's an autorelease object
auto scene =loading::createScene();
// run
director->runWithScene(scene);
return true;
}

Related

How to adjust the screen for portrait and landscape mode such that all UI elements can be seen?

I am building an app.I have kept my reference resolution game view setting as (800 x 1200) .Settings for canvas shown below
When I check the it Iphone6(750 x 1334) resolution I can see the contents but certain components are cropped with respect to width.The Game view is shown below. I use panels,vertical layout group,horizontal layout group many UI components in my application.
Now I changed my Game view resolution to ipad pro(2224 x 1668).Now I cannot see my images.The Game view is shown below
I dont know how to adjust the UI elements according to different screen sizes.Some of the answers I observed was I should keep settings as Scale with screen size,match width or height and Match slider to 0.5. I have kept these settings(see first image).Still it is not changing according to different screen sizes.
I tried Aspect ratio fitter inside the parent element(Showdata) but of no use.What should I do to change screen according to different screen sizes?Is it because I am not anchoring UI elements in a proper way?
How will you anchor something like this(Canvas>Panel(Stretched along XY)>Panel(with Vertical layout)>Many UI elements)
I have seen many tutorials on how to use anchor for images and buttons but how to use for nested elements or when we use vertical or horizontal layout and it contains many UI elements.
The Rect Transform for each UI element is given below.
Here is an abbreviated list of tips, from most to least urgent:
Set your canvas scaling to Constant Physical Size. On iPhone X devices, the scaling factor is 3x. Find other physical scale factors here. The purpose of doing this is to make your Canvas's layout coordinates correspond to device points while rendering at the native resolution of the device. This is the most urgent source of your agony.
For RectTransform components corresponding to a full screen, set its anchors to (0, 0), (1, 1), i.e., fill the parent. Play with the Top, Right, etc. properties. But it seems like you already know that.
Child elements of vertical / horizontal layout groups should have a LayoutElement and should generally be of fixed size in the non-scrolling axis, i.e., do not use ContentSizeFitter on rows for now, get it to work for a fixed height. This is almost always the correct behavior on mobile devices anyhow.
Experiment with LayoutElement's Ignore Layout flag.
Correctly set the Pixels Per Unit setting on your textures.
On mobile devices, you will miss issues like cutouts and bevels in corners. Use NotchSolution for Unity 2019.2 and earlier, and the new Device Simulator for 2019.3 and above, to visualize and adapt to these issues.
Use a recycling scroll view component, like Optimized ScrollView Adapter. This will also include thoughtful tutorials about how to lay things out. If you need content fit height elements in a recycling scroll view, you will need this asset.
To generally trigger changes based on screen layout, use OnRectTransformDimensionsChanged(). Nothing else, don't check for changes in Screen! You should familiarize yourself with the EventSystem generally.
Familiarize yourself with the aspect ratios of various devices, and use Camera.main.aspect to correctly toggle between hand-tuned layouts for these aspect ratios. There are 4 in iOS.
Here is an example of a platform canvas scaler:
using UnityEngine;
using UnityEngine.EventSystems;
#if UNITY_IPHONE
using UnityEngine.iOS;
#endif
using UnityEngine.UI;
namespace StackOverflow
{
[ExecuteInEditMode]
public sealed class PlatformCanvasScaler : UIBehaviour
{
public static PlatformCanvasScaler instance { get; private set; }
public float scaleFactor
{
get
{
if (m_CanvasScaler != null)
{
return m_CanvasScaler.scaleFactor;
}
return 1.0f;
}
}
[SerializeField] private CanvasScaler m_CanvasScaler;
[SerializeField] private float m_PhoneScaleFactor = 1.15f;
private PlatformCanvasScaler()
{
instance = this;
}
protected override void Awake()
{
base.Awake();
if (!enabled)
{
return;
}
Refresh();
}
private void Refresh()
{
var scaleFactor = 1f;
var isMobile = PlatformCanvasScaler.isMobile;
if (isMobile)
{
#if UNITY_IPHONE
switch (Device.generation)
{
case DeviceGeneration.iPhoneX:
case DeviceGeneration.iPhoneXSMax:
case DeviceGeneration.Unknown:
case DeviceGeneration.iPadUnknown:
case DeviceGeneration.iPodTouchUnknown:
scaleFactor = 3f;
break;
case DeviceGeneration.iPhone5:
case DeviceGeneration.iPhoneSE1Gen:
case DeviceGeneration.iPhone5C:
case DeviceGeneration.iPhone5S:
case DeviceGeneration.iPhone6:
case DeviceGeneration.iPhone6S:
case DeviceGeneration.iPhone7:
case DeviceGeneration.iPhone8:
case DeviceGeneration.iPhoneXR:
case DeviceGeneration.iPhoneUnknown:
scaleFactor = 2f;
break;
case DeviceGeneration.iPhone6Plus:
case DeviceGeneration.iPhone7Plus:
case DeviceGeneration.iPhone8Plus:
case DeviceGeneration.iPhone6SPlus:
scaleFactor = 3f / 1.15f;
break;
case DeviceGeneration.iPhone:
case DeviceGeneration.iPhone3G:
case DeviceGeneration.iPhone3GS:
case DeviceGeneration.iPad1Gen:
case DeviceGeneration.iPad2Gen:
case DeviceGeneration.iPad3Gen:
scaleFactor = 1f;
break;
default:
scaleFactor = 2f;
break;
}
scaleFactor *= m_PhoneScaleFactor;
#else
scaleFactor = 2f * Screen.dpi / 326f;
#endif
}
else
{
if (Screen.dpi > 140)
{
scaleFactor = 2.0f;
}
else
{
scaleFactor = 1.0f;
}
}
m_CanvasScaler.scaleFactor = scaleFactor;
}
public static bool isMobile
{
get
{
var isPhone = false;
#if UNITY_IPHONE
isPhone = true;
#endif
var isMobile = Application.platform == RuntimePlatform.IPhonePlayer ||
(Application.isEditor && isPhone);
return isMobile;
}
}
#if UNITY_EDITOR
private void OnValidate()
{
Refresh();
}
#endif
// Update is called once per frame
private void Update()
{
}
}
}
Observe I make some decisions about what is considered a HiDPI device.
Unfortunately, my favorite UI asset for screen management, MaterialUI, is deprecated. Hopefully someone can comment on their experience with others.

Android - Google Sample HdrViewFinder in Portrait Mode

can somebody help me out with the following code. I want to transform the HdrViewFinder project from google samples (https://github.com/googlesamples/android-HdrViewfinder) so that it also works in portrait mode. But the project works only in landscape mode.
In portrait mode, the camera preview gets distorted.
So, here is the method I extracted from the HdrViewfinderActivity.java class which I think is responsible for setting the whole view into landscape:
/**
* Configure the surfaceview and RS processing.
*/
private void configureSurfaces() {
// Find a good size for output - largest 16:9 aspect ratio that's less than 720p
final int MAX_WIDTH = 1280;
final float TARGET_ASPECT = 16.f / 9.f;
final float ASPECT_TOLERANCE = 0.1f;
StreamConfigurationMap configs =
mCameraInfo.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
if (configs == null) {
throw new RuntimeException("Cannot get available picture/preview sizes.");
}
Size[] outputSizes = configs.getOutputSizes(SurfaceHolder.class);
Size outputSize = outputSizes[0];
float outputAspect = (float) outputSize.getWidth() / outputSize.getHeight();
for (Size candidateSize : outputSizes) {
if (candidateSize.getWidth() > MAX_WIDTH) continue;
float candidateAspect = (float) candidateSize.getWidth() / candidateSize.getHeight();
boolean goodCandidateAspect =
Math.abs(candidateAspect - TARGET_ASPECT) < ASPECT_TOLERANCE;
boolean goodOutputAspect =
Math.abs(outputAspect - TARGET_ASPECT) < ASPECT_TOLERANCE;
if ((goodCandidateAspect && !goodOutputAspect) ||
candidateSize.getWidth() > outputSize.getWidth()) {
outputSize = candidateSize;
outputAspect = candidateAspect;
}
}
Log.i(TAG, "Resolution chosen: " + outputSize);
// Configure processing
mProcessor = new ViewfinderProcessor(mRS, outputSize);
setupProcessor();
// Configure the output view - this will fire surfaceChanged
mPreviewView.setAspectRatio(outputAspect);
mPreviewView.getHolder().setFixedSize(outputSize.getWidth(), outputSize.getHeight());
}
How could I change this method so that it works also in portrait mode? What do I need to change ?
Setting only the screenOrientation attribute in the manifest file to portrait did not help.
I have found out the 16:9 is used for full landscape mode and that 9:16 used for full portrait mode. So, I changed MAX_WIDTH to 720 and TARGET_ASPECT to 9.f/16.f . But that also did not help.
Can somebody provide a solution ?
Here is a little sketch showing the problem/what happens when I just set the screenOrientation attribute in the manifest file to portrait mode:
I have an experimental fork that achieves this, by switching to TextureView from SurfaceView.
tested on Sony G8441 a.k.a Xperia XZ1 Compact with Android Pie

QML font.pointSize varies across platforms

I know there is a huge amount of posts everywhere, however nothing realy works or at least not to me...
basicaly I dont use exact numbers for font.pointSize, i use it like:
font.pointSize: Math.min(mainappwindow.width, mainappwindow.height)/10
its to be sure that if resolution changes, the text will always be 10% of all screen (smaller from width or height obviously)...
However, if I compile on windows, the height of the text has 72points (counted, as well as debugged).
But when I compile on Android (720x1280 HD), the same code debugs again 72points, but its way bigger! coutned points and its 113...
after a week of googling and trying whatever i could try (nothing worked), i just conunted the diference ration of 0.638 what I am multiplying if platform is android....
This works fine... however when I changed resolution on the phone from HD to fullHD(1080x1920) its again bigger by ratio 1.632...
Therefore this ratio recalculation wont work on different devices which has different resolution...
I have read all the google sources, QML scaling etc, but I would realy appriciate any pice of code directed to my example...
Thank you, this makes me desperate
How #sk2212, said, you could use Qt::AA_EnableHighDpiScaling
In main.cpp file write this code
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
I'd recomended to check Text propertys: renderType, minimumPixelSize, minimumPointSize and fontSizeMode.
Check renderType and fontSizeMode. Maybe it's your sollution
In Text item try this code. And try it without your custom code
renderType: Text.QtRendering
fontSizeMode: Text.FixedSize
Also, it would be great, if you provide some code.
P.S: I'd recommended to create two versions, one for desktop, and one for phone, there will be less code and easier to maintain your app.
You can use Qt::AA_EnableHighDpiScaling see High DPI Documentation
However sometimes there are some issues with this option so you can implement your own pixel size wrapper:
import QtQuick 2.0
pragma Singleton
Object {
id: units
/*!
\internal
This holds the pixel density used for converting millimeters into pixels. This is the exact
value from \l Screen:pixelDensity, but that property only works from within a \l Window type,
so this is hardcoded here and we update it from within \l ApplicationWindow
*/
property real pixelDensity: 4.46
property real multiplier: 1.4 //default multiplier, but can be changed by user
/*!
This is the standard function to use for accessing device-independent pixels. You should use
this anywhere you need to refer to distances on the screen.
*/
function dp(number) {
return Math.round(number*((pixelDensity*25.4)/160)*multiplier);
}
function gu(number) {
return number * gridUnit
}
property int gridUnit: dp(64)
}
In your main.cpp use native Android code for density calculation:
int density = 0;
float logicalDensity = 0;
float yDpi = 0; float xDpi = 0;
#if defined(ANDROID)
QAndroidJniObject qtActivity = QAndroidJniObject::callStaticObjectMethod("org/qtproject/qt5/android/QtNative", "activity", "()Landroid/app/Activity;");
QAndroidJniObject resources = qtActivity.callObjectMethod("getResources", "()Landroid/content/res/Resources;");
QAndroidJniObject displayMetrics = resources.callObjectMethod("getDisplayMetrics", "()Landroid/util/DisplayMetrics;");
density = displayMetrics.getField<int>("densityDpi");
logicalDensity = displayMetrics.getField<float>("density");
yDpi = displayMetrics.getField<float>("ydpi");
xDpi = displayMetrics.getField<float>("xdpi");
QAndroidJniEnvironment env;
if (env->ExceptionCheck()) {
// Handle exception here.
env->ExceptionClear();
}
#endif
// Add it as a context property (see main.qml)
engine.rootContext()->setContextProperty("densityData", density);
In your main.qml add in Component.onCompleted something like this:
Units.pixelDensity = Qt.binding(function() {
if (Qt.platform.os === "android") {
return densityData / 25.4; // densityData is per inch but we need per mm
}
return Screen.pixelDensity
});
function calculateDiagonal() {
if (Qt.platform.os === "android") {
return Math.sqrt(Math.pow(Screen.width, 2) +
Math.pow(Screen.height, 2)) / densityData;
}
return Math.sqrt(Math.pow(Screen.width, 2) +
Math.pow(Screen.height, 2)) / (Screen.pixelDensity * 25.4);
}
Units.multiplier = Qt.binding(function() {
var diagonal = calculateDiagonal();
Device.diagonal = diagonal;
var baseMultiplier = 1;
if (diagonal >= 3.5 && diagonal < 5.1) { //iPhone 1st generation to phablet
return 0.8;
} else if (diagonal >= 5.1 && diagonal < 6.5) {
return 1;
} else if (diagonal >= 6.5 && diagonal < 15.1) {
return baseMultiplier;
} else if (diagonal >= 15.1 && diagonal < 29) {
return 1.4 * baseMultiplier;
} else if (diagonal >= 29 && diagonal < 92) {
return 1.4 * baseMultiplier;
} else {
return 1.4 * baseMultiplier;
}
});
Use for every pixel value:
Rectangle {
height: Units.dp(50)
width: Units.dp(30)
}

Phaser: Make background screen-width instead of content-width

I am making a game using the Phaser Framework for Android. I need to make the canvas fill the entire screen width and then flood it with background color, then put content in the center of screen. Currently, even though I already set the width of the canvas to window width, Phaser still reset it to content width, which is smaller, then filling only the content width with background.
How do I stop Phaser from using content width?
var gameObj = new Phaser.Game($(".parent").width(), 700, Phaser.CANVAS, 'parent');
gameObj.stage.backgroundColor = '#A6E5F5';
gameObj.load.image('prld', 'prl.png');
gameObj.scale.scaleMode = Phaser.ScaleManager.SHOW_ALL;
gameObj.scale.pageAlignHorizontally = true;
gameObj.scale.pageAlignVertically = true;
gameObj.scale.setScreenSize(true);
gameObj.scale.refresh();
gameObj.stage.backgroundColor = '#A6E5F5';
// Load Content Here
gameObj.load.image('h1', 'res1/h1.png');
gameObj.load.image('h2', 'res1/h2.png');
....
g = gameObj.add.group();
g.create(20, 20, 'h1');
g.create(60, 20, 'h2');
....
You need Phaser 2.2+ for this, but if you want the canvas to fill the entire screen space available you would do:
var game = new Phaser.Game("100%", "100%", Phaser.CANVAS, 'parent');
...
game.scale.scaleMode = Phaser.ScaleManager.RESIZE;
Also you should not use pageAlign if you're using 100% dimensions. You also don't need to call refresh or setScreenSize.
When working in the RESIZE scale mode you can add a function to your States called 'resize'. It will be called whenever the screen dimensions change (orientation event perhaps) and be passed 2 parameters: width and height. Use those to adjust your game content layout.
You may want to get the Phaser Scale Manager book, it covers all this in lots more detail (caveat: I wrote it, but it has a "pay what you want" price, including completely free)
i use this code in my game and it work very nice without changing in position of any elements
var targetWidth = 720; // the width of the game we want
var targetHeight = 480; // the height of the game we want
// additional ratios
//Small – 360x240
//Normal – 480x320
//Large – 720x480
//XLarge – 960x640
//XXLarge – 1440x960
var deviceRatio = (window.innerWidth/window.innerHeight); //device aspect ratio
var newRatio = (targetHeight/targetWidth)*deviceRatio; //new ratio to fit the screen
var newWidth = targetWidth*newRatio;
var newHeight = targetHeight;
var gameWidth = newWidth;
var gameHeight = newHeight;
var gameRendrer = Phaser.AUTO;
and this code for Boot state
game.scale.scaleMode = Phaser.ScaleManager.SHOW_ALL;
game.scale.pageAlignHorizontally = true;
game.scale.pageAlignVertically = true;
game.scale.forceLandscape = true;
game.scale.setScreenSize(true);
this for landscape mode if you want to make it for portrait change width and height like
var targetWidth = 480; // the width of the game we want
var targetHeight = 720; // the height of the game we want
also force it to portrait
this code work well in my games

How to adjust images to all android screen devices using cocos2d android library without using xml layout for Game designing

I am new to Android Game development, I am facing problem with adjusting images to all different screen sizes of android.
Technology I am using cocos2d android library to design game.
Currently working code is :-
if(winSize.width>1250 && winSize.height>700)
{
background = CCSprite.sprite("bg480X800.png");
float scalexx=winSize.width/background.getContentSize().width;
float scaleyy=winSize.height/background.getContentSize().height;
background.setScaleX(scalexx);
background.setScaleY(scaleyy);
//background.setScale(1.65f);
}
/* *************************1000-1250 ********************** */
else if(winSize.width<1250 && winSize.width>1000 )
{
background = CCSprite.sprite("bg7.png");
float scalexx=winSize.width/background.getContentSize().width;
float scaleyy=winSize.height/background.getContentSize().height;
background.setScaleX(scalexx);
background.setScaleY(scaleyy);
//background.setScale(1.0f);
}
else
{
background = CCSprite.sprite("bg480X800.png");
//background.setScale(1.0f);
float scalexx=winSize.width/background.getContentSize().width;
float scaleyy=winSize.height/background.getContentSize().height;
background.setScaleX(scalexx);
background.setScaleY(scaleyy);
}
Please tell me if you know better and smart coding that it adjust to all screen sizes
CGSize winSize = CCDirector.sharedDirector().displaySize();
CCSprite background=CCSprite.sprite("background.png");
background.setScaleX(winSize .width/background.getTexture().getWidth());
background.setScaleY(winSize.height/background.getTexture().getHeight());
background.setPosition(CGPoint.make(winSize.width/2, winSize.height/2));
addChild(background);
Set to exact , what is the screen size

Categories

Resources