Author Topic: Discussion of creating Aspect-Aware Layouts  (Read 27600 times)

liquid8d

  • Global Moderator
  • Sr. Member
  • *****
  • Posts: 442
    • View Profile
Discussion of creating Aspect-Aware Layouts
« on: June 15, 2015, 10:04:00 PM »
Continuing discussion of:
http://forum.attractmode.org/index.php?topic=272.0

I'd like to see a sort of unified way of designing layouts, ensuring they work well for all aspect ratios. Many people design their layouts with a single resolution / aspect in mind which will scale/stretch but not look as nice in other aspects / resolutions.

There's two things I'd like to see to help with this:

Storing 'set' values at the top of your layout, then set them from the variables in your layout code

Here's an example:
Code: [Select]
LAYOUT_SETTINGS <- {
   rotate_90 = {
      width = fe.layout.height,
      height = fe.layout.width,
      title = { x = 0, y = fe.layout.height / 1.08 },
      wheel = { x = 0, y = fe.layout.height / 1.20, x_divisor = 2.8 },
   },
   default = {
      width = fe.layout.width,
      height = fe.layout.height,
      title = { x = 0, y = fe.layout.height / 1.10 },
      wheel = { x = 0, y = fe.layout.height / 1.33, x_divisor = 2.4 },
   }
}

local values = LAYOUT_SETTINGS["default"];
if (( actual_rotation == RotateScreen.Left ) || ( actual_rotation == RotateScreen.Right )) values = LAYOUT_SETTINGS["rotate_90"];

//now we just reference values for everything when setting positions and sizes for objects...
title.x = values.title.x;
wheel.y = values.wheel.y;

This is a personal preference, but tables are a good way to store a lot of values and a lot of values within values without your code getting out of hand. It's is however important to be careful with table syntax and formatting it for good readability. It's  also better than arrays for readability because you will use word references like values.title.y instead of values[2][1]. You can also store alternate settings for your objects and keep the original values to reference back if needed.

You wouldn't have to use tables, but it's a good idea for all your initial values to be in one location, preferably at the top of the layout code.

Prepare layouts that will work in multiple aspects

Most major changes to your layout will probably be objects position and size.

If you go by my table suggestion, you can have a table that stores settings for each object at each individual aspect / resolution you want to support, then reference something like values[current_aspect].title.x in your code.

Otherwise, if you just keep all your settings in variables at the top, it will be easy to make a copy of your layout, store it as layout-altaspect.nut and adjust your variables to work with the new aspects / resolution.

Either way, we could make it easy to add a user config option of Aspect: standard,widescreen,etc. and use the correct values or load the appropriate layout.

Just some ideas I had when it comes to cleaning up code and making it easier to deal with multiple layout configurations. Perhaps a couple templates for creating multiple-aspect layouts would be helpful?


verion

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 861
    • View Profile
    • new projects
Re: Discussion of creating Aspect-Aware Layouts
« Reply #1 on: June 16, 2015, 01:18:43 AM »
What I would like to use is some kind of mechanism that is working with responsive web pages.

align to left/right/top/bottom
center horizontally/center vertically.

Like with responsive webpages - you can design layout with that flexibility in mind - with object scaling related to screen width and wisely applied white (fill) space.

----

But of course this is more "lazy" approach - it can be combined with tables for fine tuning of the design. But more like overwriting the layout defaults (like in CSS).
« Last Edit: June 16, 2015, 01:23:54 AM by verion »

Luke_Nukem

  • Sr. Member
  • ****
  • Posts: 135
    • View Profile
    • Blogging about Rust lang
Re: Discussion of creating Aspect-Aware Layouts
« Reply #2 on: June 16, 2015, 03:47:16 AM »
I just pushed some major changes to my github.

The Swapper layout now uses a proof of concept way of doing a universal layout. It happens to highlight quite a few things that you need to do also.
One of those things is to make functions to update each object on your screen, as AM won't do it for you.

Another thing, "Align.Centre" won't work if you try and call it after setting a new screen width and height, so you'll have to do centering manually.

I'll probably do some more major hacking tomorrow. Out of time tonight, but at least I got this done.

(PS: I've created a "shaders" dir in my fork of attractmode-extras. They're all Mame usable shaders, but can easily be used in AM by just making sure you call;
Code: [Select]
                videoShader.set_param( "color_texture_sz", 640, 480 );
                videoShader.set_param( "color_texture_pow2_sz", 640, 480 );
                videoShader.set_texture_param( "mpass_texture" );
color_texture** should probably be set by something like video.width and video.height, but will need a callback to update it on each change)

Shader dir = https://github.com/Luke-Nukem/attract-extra/tree/master/layouts/shaders
« Last Edit: June 16, 2015, 03:51:01 AM by Luke_Nukem »

liquid8d

  • Global Moderator
  • Sr. Member
  • *****
  • Posts: 442
    • View Profile
Re: Discussion of creating Aspect-Aware Layouts
« Reply #3 on: June 16, 2015, 06:54:07 AM »
Hah! You beat me to it :) I'll check out what you did in there in a bit.

verion,
The positioning you are talking about is doable - I have functions in my animate module that position an object at "topleft/center/bottomright", etc.. based on the layout width/height and the object width/height. It can be a bit complicated though and I also think that could limit design creativity:

https://raw.githubusercontent.com/liquid8d/attract-extra/master/extras/positions.png

I've also created functions to place things by percentages, but you would still have to have aspect / resolution specific percentages.


Here's what we have to deal with:
* Position
* Size
* Other object settings like Rotation/Skew/Pinch
* Font / Font Size
* Images
* Other non-object variables that might change dependent upon aspect or resolution

This is why I think it's best to specify values manually for each, and do it all in one spot. A default one can be used to "fall back" to. Kodi/XBMC uses a similar method to what I'm talking about, having folders for various aspects or resolutions - and it will pick and match the best one suited for the users resolution:
Quote
The order that XBMC looks for it's skin files are as follows:

    It first looks in the current screenmode folder (one of 1080i, 720p, NTSC16x9, NTSC, PAL16x9 or PAL)
    If the current screenmode is 1080i and there's no 1080i folder, it then looks in the 720p folder.
    Finally, it looks in the res folder.


Here's what I've come up with so far:
Code: [Select]
class UserConfig </ help="Intro Layout Options" />
{
</ label="Screenmode", help="Pick your preferred screen mode", options="4x3,720p,1080p" order=1 />
screenmode="4x3";
}

local config = fe.get_config();

print( "Using screen mode: " + config["screenmode"] + "\n" );
//set the width/height based on the choosen screen mode
fe.layout.width = config["screenmode"].width;
fe.layout.height = config["screenmode"].height;

//ResourcePath(file) will return the best path based on the user selected aspect/resolution
ResourcePath <- function( file ) {
    //here we can do a fallback match - exact, best match or default path
    return config["screenmode"] + "/" + file;
}

//Store all SETTINGS for each aspect / resolution you want to support here
// This might be as simple as object property values or your own varibles that might
// determine a change in a layout based on aspect / resolution
SETTINGS <- {
    //every setting MUST be defined in default (this will be the fallback value)
    "default": {
        "width": 640,
        "height": 480,
        "logo": { src = ResourcePath("bg.png"), x = 0, y = 0, width = fe.layout.width, height = fe.layout.height },
        "title": { x = 0, y = 450, width = 640, height = 30 },
        "snap": { x = 0, y = 0 }
        "fontName": "Arial",
        "animate_wheel": false
    },
    //aspect-resolution specific values can be added for each
    "720p": {
        "width": 1280,
        "height": 720,
        "title": { x = 0, y = 0, width = 1280, height = 30 },
        "animate_wheel": true
    },
    "1080p": {
        "width": 1920,
        "height": 1080
    }
}

//Based on the requested aspect-resolution, the correct settings from above will be stored here
Setting <- {}
foreach ( name, val in SETTINGS[ config["screenmode"] ] )
{
    //here we can do a fallback match - exact, best match or default value
    Setting[name] <- val;
}


/////////////////////////////////////////////////////////////////////////////
// Now we start 'coding' our layout. Just use Setting.? to access any properties
// or variables we configured
//
// This part could just be in the screen mode folder and we could just do this to end the main layout.nut file:
// fe.do_nut( Resource( "layout.nut" ) );
/////////////////////////////////////////////////////////////////////////////
local logo = fe.add_image( Setting.logo.src, Setting.logo.x, Setting.logo.y, Setting.logo.width, Setting.logo.height );
local title = fe.add_text( "[Title]", Setting.title.x, Setting.title.y, Setting.title.width, Setting.title.height );

I could put a lot of the excess stuff here in a module and the user would just include the module, add their settings and be up and running.

* In the SETTINGS table, you set default values and then any aspect-resolution dependent values
* It would automatically fallback to the best match from all your settings.
* All values would be available via Setting.value - for example Setting.title.width would return 1280 for the 720p mode and 640 for the 4x3 mode.

That's still a WIP that would be based on what we discuss here, but hopefully it's clear enough. I don't want to make it MORE difficult to support mutiple setups - it should be MUCH LESS difficult.


Let me know what you think!

omegaman

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 880
    • View Profile
Re: Discussion of creating Aspect-Aware Layouts
« Reply #4 on: June 16, 2015, 07:50:35 AM »
Hi all!
I've been busy, so I thought it was important to catch up on this topic and offer my 2 cents worth. It’s my basic understanding that when designing layouts for all screen resolutions you do it in terms of pixels or percentages. With the latter being the preferred method. Once that is decided, you need to determine what will be the lowest screen resolution that is supported. For example, if you decide that 640x480 will be the lowest res that anyone would use in their layouts you would design all your graphic elements at this resolution. Using these methods you should be able to scale up to whatever resolution along as it’s in the same aspect like 4:3 or 16x9.

And, this is essentially what I do in my layouts now except that I use pixel position. I design a layout based on either 1024x768 or 1024x1024 res and scale from there. Though, the robospin theme was done at the latter resolution and probably should have been done in 1024x768.  Regardless, my layouts will scale proportionally and look good enough, at least to my eyes. Check out my grey theme or robospin to get an idea of what I’m talking about. 

Now, all that said and to Liquid8’s point. There is no doubt that using percentages and tables are going to be the more eloquent solution. Maybe, something like a table supporting all resolutions with width and height @ 100% or whatever percent you want.  Then you would use fixed measurements for rows and columns. Something like first and last columns given fixed measurements of px. The middle column would be given no measurement and would scale to fit page. And, all the rows would have a fixed height except for the content row. This based on  stuff I’ve read on website design.

No matter what method you use, I think there will always be some stretching based on the aspect ratios, though a table or grid would definitely minimize this. It is the nature of the beast.
IMHO, wide screen resolutions are just ugly for arcade layouts. And, the only thing that even remotely looks good using them are grid type layouts.   
This is my 2 cents worth.





omegaman

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 880
    • View Profile
Re: Discussion of creating Aspect-Aware Layouts
« Reply #5 on: June 16, 2015, 07:51:40 AM »
Responsive web pages still have there issues as well.

verion

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 861
    • View Profile
    • new projects
Re: Discussion of creating Aspect-Aware Layouts
« Reply #6 on: June 16, 2015, 08:28:46 AM »
@omegaman

Quote
For example, if you decide that 640x480 will be the lowest res that anyone would use in their layouts you would design all your graphic elements at this resolution.

And not the oposite? Shoot for highest possible resolution and scale down? Like designing for 1280 x 1024 and scale down to 640x480. Bitmaps scales down perfectly without loosing quality. Scaling up is not that great.

EDIT: Ahhhh... Now I get it - You should aim for the lowest resolution in terms of SIZE and VISIBILITY of the elements displayed on the screen. Going from hi-res to low-res can result in the whole interface being too small. I can (and should) design in hi-res but I should keep in mind how it will look on low-res screen.

----

I agree that some stretching is ok. But stretching from 16:9 to 16:10 or the other way.
Not from 4:3 to 16:9 - that is too much (for me - of course).

----

@ Luke-Nukem, Liquid8d

That looks very promising.
« Last Edit: June 17, 2015, 03:08:11 AM by verion »

Luke_Nukem

  • Sr. Member
  • ****
  • Posts: 135
    • View Profile
    • Blogging about Rust lang
Re: Discussion of creating Aspect-Aware Layouts
« Reply #7 on: June 16, 2015, 07:18:05 PM »
I've just made a big push to Git. https://github.com/Luke-Nukem/attract-extra/tree/master/layouts/rotating_basic

New layout, uses the bg art from basic as an example.
It's a really good example of scaling a single layout, with fixed aspect ratio. Can someone test and confirm that it does indeed scale to other resolutions okay?

I've basically made one main piece of art, the bg.png. This one scales to each resolution and rotation, while keeping the correct aspect ratio for itself.
Every other bit of art, surfaces, videos, text, all base their co-ordinates and width/height on the BG art, via a set of ratios.

In theory, the same methods I've used here, should also work well for going in the other direction. I.e from larger art to smaller, since everything is getting their settings off the one art bit.

The only thing I'm really worried about is;
Code: [Select]
//gameList.charsize = 26;
gameList.charsize = (overlay.height / 18.6);
« Last Edit: June 16, 2015, 07:21:21 PM by Luke_Nukem »

Luke_Nukem

  • Sr. Member
  • ****
  • Posts: 135
    • View Profile
    • Blogging about Rust lang
Re: Discussion of creating Aspect-Aware Layouts
« Reply #8 on: June 16, 2015, 10:32:18 PM »
I have also made another layout based on the last one, but using a different BG for horizontal/vertical orientations.

Everything should scale nicely.
https://github.com/Luke-Nukem/attract-extra/tree/master/layouts/rotating_new

verion

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 861
    • View Profile
    • new projects
Re: Discussion of creating Aspect-Aware Layouts
« Reply #9 on: June 17, 2015, 03:20:59 AM »
I don't know what to expect - but it looks like this on 4:3 and 16:9 screen.
Something is wrong.

Luke_Nukem

  • Sr. Member
  • ****
  • Posts: 135
    • View Profile
    • Blogging about Rust lang
Re: Discussion of creating Aspect-Aware Layouts
« Reply #10 on: June 17, 2015, 04:46:27 AM »
Thanks dude. Should be an easy fix, but will need to do it tomorrow.

Part was through building a module to report aspect ratio.

Luke_Nukem

  • Sr. Member
  • ****
  • Posts: 135
    • View Profile
    • Blogging about Rust lang
Re: Discussion of creating Aspect-Aware Layouts
« Reply #11 on: June 17, 2015, 04:52:04 AM »
Actually, in set_overlay.nut, in function overlayUpdate_Horz change this line;

    local ratio = fe.layout.height / overlay_horz.texture_height;

To;

local ratio = fe.layout.height / overlay_horz.texture_width;

verion

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 861
    • View Profile
    • new projects
Re: Discussion of creating Aspect-Aware Layouts
« Reply #12 on: June 17, 2015, 05:10:12 AM »
Nope. It doesn't fix that.

omegaman

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 880
    • View Profile
Re: Discussion of creating Aspect-Aware Layouts
« Reply #13 on: June 17, 2015, 05:55:33 AM »
verion-

Well, it's my understanding that you always start with a minimum resolution. And, I think that is because most PC users use 1024x768 or greater. I see what you are getting at though. Why don't you test it with roboskin? Just set your resolution to a maxium then tweak the layout.nut to the new size and see how it scales down. I would backup the original .nut though.
« Last Edit: June 17, 2015, 06:01:40 AM by omegaman »

omegaman

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 880
    • View Profile
Re: Discussion of creating Aspect-Aware Layouts
« Reply #14 on: June 17, 2015, 06:54:39 AM »
Luke-

My newer layouts already scale based on the base resolution the layout was designed for. The only thing I see that you are doing differently - and, forgive my ignorance on this, is you are creating a function for the math and calling other .nuts with the pixel position based on the BG. That and you can switch from horizontal to vertical which is nice. But, will this code scale much better than what I'm doing below. This is more for learning than a critique.     



class UserConfig {
   </ label="SpinWheel", help="The artwork to spin", options="marquee,wheel" /> orbit_art="wheel";
   </ label="Display Flyer", help="Display the flyer/game box in background.", options="Yes,No" /> enable_flyer="Yes";
   </ label="Enable Background", help="Enable/Disable background", options="Yes,No" /> enable_BG="Yes";
   </ label="Enable BG mask", help="Enable/Disable mask", options="Yes,No" /> enable_BG_mask="Yes";
   </ label="Enable Year", help="Enable/Disable year", options="Yes,No" /> enable_year="Yes";
}

local config = fe.get_config();
local flx = fe.layout.width;
local fly = fe.layout.height;
local flw = fe.layout.width;
local flh = fe.layout.height;
fe.layout.font="coolvetica";

//load animate module
fe.load_module("animate");

//background image
if (config.enable_BG == "Yes") {
fe.add_image( "bg.png", flx*0.0, fly*0.0, flw, flh);
}

//background mask
if (config.enable_BG_mask == "Yes") {
fe.add_image( "mask.png", flx*0.0, fly*0.0, flw, flh);
}

//property animation for snap
local snap = fe.add_artwork("snap", flx*0.1, fly/3, flw/2.8, flh/2.3);                   
snap.preserve_aspect_ratio = false;

local pos_cfg = { when = Transition.ToNewSelection,
     property = "position",
    start = { x = -flx*1.0, y = fly/3.5 },
    end = { x = flx*0.1, y = fly/3 }, time = 1000 }

animation.add( PropertyAnimation( snap, pos_cfg ) );

//Display flyer on right
if (config.enable_flyer == "Yes") {
local flyer = fe.add_artwork("flyer", flx/1.7, fly/3.9, flw/3.6, flh/1.8);                   
flyer.rotation = 3

local alpha_cfg = {
    when = Transition.ToNewSelection,
    property = "alpha",
    start = 0,
    end = 150,
    time = 1000
}
animation.add( PropertyAnimation( flyer, alpha_cfg ) );

local pos_cfg = { when = Transition.ToNewSelection,
     property = "position",
    start = { x = flx*8.0, y = fly/3.8 },
    end = { x = flx/1.7, y = fly/3.9 }, time = 1000 }

animation.add( PropertyAnimation( flyer, pos_cfg ) );
}


local title = fe.add_text("[Title]", flx*0.0, fly/1.25, flw, flh*0.025);
title.set_rgb( 220, 220, 220 );
//title.align = Align.Right;

local scale_cfg =
{ when = Transition.ToNewSelection,
 property = "scale",
 start = 1.0,
 end = 1.5,
 time = 1500 }

animation.add( PropertyAnimation( title, scale_cfg ) );

local pos_cfg = { when = Transition.ToNewSelection,
     property = "position",
    start = { x = flx*0.0, y = fly/1 +100 },
    end = { x = flx*0.0, y = fly/1.25 }, time = 1000 }

animation.add( PropertyAnimation( title, pos_cfg ) );
 
//text stuff

 
if (config.enable_year == "Yes") {
//local yearshadow = fe.add_text( "[Year]", flx*0.42, fly/8, flw*0.4, flh*0.08 );
//yearshadow.set_rgb( 10, 10, 10 );
//yearshadow.align = Align.Left;
//yearshadow.rotation = 0;

local year = fe.add_text( "[Year]", flx*0.41, fly/95, flw*0.4, flh*0.08 );
year.set_rgb( 255, 255, 255 );
year.align = Align.Left;
year.rotation = 0;
year.alpha = 220;

animation.add( PropertyAnimation( year, {
when = Transition.ToNewSelection,
property = "y",
start = -180, end = year.y,
tween = Tween.Bounce,
time = 2500 } ) );

local pos_cfg = { when = Transition.ToNewSelection,
     property = "position",
    start = { x = flx*0.41, y = -fly*0.5  },
    end = { x = flx*0.41, y = fly/95 }, time = 1000 }
     animation.add( PropertyAnimation( year, pos_cfg ) );
}

local msgplay = fe.add_text( "Played :" , flx*0.9, fly*0.01, flw*0.3, flh*0.026 );
msgplay.set_rgb( 255, 255, 255 );
msgplay.align = Align.Left;   

local play = fe.add_text( "[PlayedCount]", flx*0.96, fly*0.01, flw*0.3, flh*0.026 );
play.set_rgb( 255, 255, 255 );
play.align = Align.Left;   

local filter = fe.add_text( "[ListFilterName]: [ListEntry]-[ListSize]", flx*0.01, fly*0.01, flw*0.3, flh*0.026 );
filter.set_rgb( 255, 255, 255 );
filter.align = Align.Left;
filter.rotation = 0;

fe.load_module( "conveyor" );
fe.load_module( "fade" );

class SimpleArtStrip extends Conveyor
{
   m_x=0; m_y=0; m_width=0; m_height=0; m_x_span=0; m_y_span=0;

   constructor( artwork_label, num_objs, x, y, width, height, pad=0 )
   {
      base.constructor();
      local my_list = [];
      for ( local i=0; i<num_objs; i++ )
         my_list.push( SimpleArtStripSlot(this,artwork_label) );
      set_slots( my_list );

      m_x=x+pad/2; m_y=y+pad/2;
      if ( width < height )
      {
         m_x_span=0;
         m_y_span=height;
         m_width=width-pad;
         m_height=height/m_objs.len()-pad;
      }
      else
      {
         m_x_span=width;
         m_y_span=0;
         m_width=width/m_objs.len()-pad;
         m_height=height-pad;
      }

      reset_progress();
   }
};

local my_strip = SimpleArtStrip( "wheel", 5, flx*0.0, fly*0.85 fe.layout.width/1.0, fe.layout.height/7.0, 1 );
my_strip.alpha = 255;

//marquee property animation
local marquee = fe.add_artwork("marquee", flx/4.6, fly/8, flw/1.8, flh/4.5);
local alpha_cfg = {
    when = Transition.ToNewSelection,
    property = "alpha",
    start = 40,
    end = 220,
    time = 1000
}
animation.add( PropertyAnimation( marquee, alpha_cfg ) );

local pos_cfg = { when = Transition.ToNewSelection,
     property = "position",
    start = { x = flx/4.6, y = -fly*0.5 },
    end = { x = flx/4.6, y = fly/8 }, time = 1000 }

animation.add( PropertyAnimation( marquee, pos_cfg ) );

             
local message = fe.add_text("Ready Player", flx*0.0, fly*0.4, fe.layout.width,80);
message.alpha = 0;
message.style = Style.Bold;

// Transitions
fe.add_transition_callback( "fancy_transitions" );

function fancy_transitions( ttype, var, ttime ) {
 switch ( ttype )
 {
 case Transition.StartLayout:
 case Transition.ToNewList:
 case Transition.ToNewSelection:
 case Transition.EndLayout:
  break;

 case Transition.FromGame:
  if ( ttime < 255 )
  {
   foreach (o in fe.obj)
    o.alpha = ttime;
    message.alpha = 0;     
     return true;
  }
  else
  {
   foreach (o in fe.obj)
    o.alpha = 255;
   message.alpha = 0;
 }
  break;
   
 case Transition.EndLayout:
  if ( ttime < 255 )
  {
   foreach (o in fe.obj)
    o.alpha = 255 - ttime;
   message.alpha = 0;
     return true;
  }
  else
  {
   foreach (o in fe.obj)
     o.alpha = 255;
    message.alpha = 0;
  }
  break;
     
 case Transition.ToGame:
  if ( ttime < 255 )
  {
   foreach (o in fe.obj)
    o.alpha = 255 - ttime;
    message.alpha = ttime;
    return true;
  }   
  break;
 }
 return false;
}