sproutcore/sproutcore — pull request #1140 by publickeating (2013-12-05 20:51:08+00:00)
Title: Design Modes

See demo: http://demos.sproutcore.com/design_mode_demo.html

While a "design" (the manner views are positioned, shaped and styled) may be flexible enough to stretch up for a large display and to compress down for a medium sized display, at a certain point it often makes more sense to stop stretching and compressing and just implement an additional new design specific to the much different display size. In order to make this possible and with as much ease as possible, SproutCore includes support for "design modes". Design modes are based on the current display size and orientation.

SproutCore supports three size-based design modes by default: 's' for small, 'm' for medium and 'l' for large. Smartphones and handheld devices like the iPod Touch fall within the small category, tablets and normal desktop displays fall within the medium category and retina desktops or 4K displays fall into the large category.

When the display size crosses a threshold between one size category to another, SproutCore will update the design mode of each view in the application, giving you a chance to provide overrides for that specific size via the special `modeAdjust` property.

For example, if you wanted to hide a view completely when in the small (s) mode you could add:

//...

mediumPlusView: SC.View.extend({

// Design mode overrides.
modeAdjust: { s: { isVisible: false } } // Hide the view in 's' or 'small' mode.

}),

//...

As you can see, we simply indicate the property overrides that we want for the specific mode. To adjust the height for medium mode, you could add:

//...

myView: SC.View.extend({

// The normal layout always applies.
layout: { height: 24 },

// Design mode overrides.
modeAdjust: { m: { layout: { height: 30 } } // Adjust the height in 'm' or 'medium' mode.

}),

//...

Note that the values in `modeAdjust` are overrides for that mode and the values will be *reset* to their original values when leaving that mode.

The second component to design modes is orientation. Each of the size categories can have two different orientations: 'l' for landscape or 'p' for portrait. Therefore, you may want to alter the design to account for the device orientation as well using `modeAdjust`. To do this, you simply specify orientation specific designs with the `_l` or `_p` suffix accordingly.

For example, you can provide a configuration for a size category with slight deviations for orientations of that size all in just a few lines of code,

//...

customView: SC.View.extend({

// The default alignment for this custom view's contents.
alignment: SC.ALIGN_LEFT,

// The default line height for this custom view's contents.
lineHeight: 40,

// Design mode overrides.
modeAdjust: {
m: { lineHeight: 50 }, // Overrides for medium mode regardless of orientation.
m_p: { alignment: SC.ALIGN_CENTER }, // Overrides for medium - portrait mode.
m_l: { layout: { top: 20 } } // Overrides for medium - landscape mode.
}

}),

//...

### A note on styling for design modes

Class names are automatically applied to each view depending on the mode as found in the SC.DESIGN_MODE_CLASS_NAMES hash. By default, your views will have one of three class names added:

> 'sc-small' in small mode
> 'sc-medium' in medium mode
> 'sc-large' in large mode

As well, the `body` element is given an orientation class name that you can use as well:

> 'sc-landscape' in landscape orientation
> 'sc-portrait' in portrait orientation

### A note on overriding layouts

Layout overrides work slightly differently than regular property overrides, because they are set via `adjust`. This means they apply on *top* of the default layout, they don't replace the default layout. For example, the default layout is `{ left: 0, right: 0, top: 0, bottom: 0 }` and if we provide a design mode like,

modeAdjust: { l: { layout: { top: 50 } } }

The layout becomes `{ left: 0, right: 0, top: 50, bottom: 0 }`. If we had a default layout like `{ centerX: 0, centerY: 0, height: 100, width: 100 }` and we wanted to change it to a left positioned layout, we would need to null out the centerX value like so,

modeAdjust: { l: { layout: { centerX: null, left: 0 } } } // Convert to left positioned layout.

### A note on the medium category

The medium category covers tablets *and* non-retina desktops and laptops. While we could try to further differentiate between these two categories, there is no safe way to do this and to do so would cause more harm than good. Tablets can be connected to mice and keyboards, desktops can have touch screens and there is no way to know whether a mouse, touch or pointer is going to be used from one event to the next. Therefore the message should be clear, *you should always design for touch*. This means that a medium sized design should be expected to work well on a laptop and a tablet.

### A note on customizing the design mode categories

Design mode thresholds are determined by the area of the display divided by the device pixel ratio. In this manner a 1024 x 768 display on a handheld device can be differentiated from a 1024 x 768 display on a desktop. Through testing and research, the three categories of 'small', 'medium' and 'large' were chosen with thresholds between them of 500,000 sq.px and 2,000,000 sq.px.

Therefore, any display area divided by device pixel ratio that is less than 500,000 will be considered 'small' and likewise a calculated area of over 2,000,000 will be considered 'large'. This should be sufficient for almost all device specific designs and as is mentioned earlier, trying to get even more fine-grained is a dangerous endeavor. However, you can still set your own thresholds easily enough by overriding the designModes property of SC.Application.