ellyjones | dc3cb5d | 2016-05-09 13:55:53 | [diff] [blame] | 1 | # Views Platform Styling |
| 2 | |
| 3 | ## Overview |
| 4 | |
| 5 | Views controls may have different appearances on different platforms, so that |
| 6 | Views UIs can fit better into the platform's native styling. This document |
| 7 | describes how to build Views UIs that will look good on all platforms with a |
| 8 | minimum of manual intervention. |
| 9 | |
| 10 | UIs looking good happens at two levels: first, the individual controls must look |
| 11 | and act appropriately for their platform, and second, the overall layout of the |
| 12 | controls in a dialog or UI surface must match what users of the platform would |
| 13 | expect. There are differences at both of these layers between desktop platforms, |
| 14 | and mobile platforms have still more differences. |
| 15 | |
| 16 | ## Controls |
| 17 | |
| 18 | Individual controls have different looks and behaviors on different platforms. |
| 19 | If you're adding a new control or a subclass of an existing control, there are |
| 20 | some best practices you should follow in designing it so that it works well |
| 21 | everywhere: |
| 22 | |
| 23 | ### Use PlatformStyle for stylistic elements |
| 24 | |
| 25 | PlatformStyle exposes factory functions that produce different subclasses of |
| 26 | Border, Background, and so on that are appropriate to the current platform. If |
| 27 | your class needs a special kind of border or another stylistic element, creating |
| 28 | it through a factory function in PlatformStyle will make per-platform styling |
| 29 | for it easier, and will make which parts of the appearance are platform-specific |
| 30 | more apparent. For example, if you were adding a Foo control that had a special |
| 31 | FooBackground background, you might add a function to PlatformStyle: |
| 32 | |
| 33 | unique_ptr<FooBackground> CreateFooBackground(); |
| 34 | |
| 35 | and a default implementation in PlatformStyle. This way, in future a |
| 36 | platform-specific implementation can go in PlatformStyleBar and change the |
| 37 | background of that control on platform Bar without changing the implementation |
| 38 | of the Foo control at all. |
| 39 | |
| 40 | ### Use PlatformStyle to add simple behavior switches |
| 41 | |
| 42 | When adding platform-specific behavior for an existing control, if possible, it |
| 43 | is useful to implement the switch using a const boolean exported from |
| 44 | PlatformStyle, instead of ifdefs inside the control's implementation. For |
| 45 | example, instead of: |
| 46 | |
| 47 | #if defined(OS_BAR) |
| 48 | void Foo::DoThing() { ... } |
| 49 | #else |
| 50 | void Foo::DoThing() { ... } |
| 51 | #endif |
| 52 | |
| 53 | It's better to do this: |
| 54 | |
| 55 | Foo::Foo() : does_thing_that_way_(PlatformStyle::kFooDoesThingThatWay) |
| 56 | |
| 57 | void Foo::DoThing() { |
| 58 | if (does_thing_that_way_) |
| 59 | ... |
| 60 | else |
| 61 | ... |
| 62 | } |
| 63 | |
| 64 | This pattern makes it possible to unit-test all the different platform behaviors |
| 65 | on one platform. |
| 66 | |
| 67 | ### Use subclassing to add complex behavior switches |
| 68 | |
| 69 | If a lot of the behavior of Foo needs to change per-platform, creating |
| 70 | platform-specific subclasses of Foo and a factory method on Foo that creates the |
| 71 | appropriate subclass for the platform is easier to read and understand than |
| 72 | having ifdefs or lots of control flow inside Foo to implement per-platform |
| 73 | behavior. |
| 74 | |
| 75 | Note that it's best only to do this when no other alternative presents itself, |
| 76 | because having multiple subclasses to do different behaviors per-platform makes |
| 77 | subclassing a control require one subclass per platform as well. It's better to |
| 78 | abstract the per-platform behavior into a separate model class, with a factory |
| 79 | that produces the right model for the current platform. |
| 80 | |
| 81 | ## UI Layout / Controls |
| 82 | |
| 83 | TODO(ellyjones): This section needs a bit more thought. |
| 84 | |
| 85 | Some platforms have conventions about the ordering of buttons in dialogs, or the |
| 86 | presence or absence of certain common controls. For example, on Mac, dialogs are |
| 87 | expected to have their "default" button at the bottom right, and expected not to |
| 88 | have a "close" button in their top corner if they have a "Cancel"/"Dismiss" |
| 89 | button in the dialog body. If you can design a layout that follows all |
| 90 | platforms' conventions simultaneously, that is the lowest-effort route to |
| 91 | follow, but if not, there are static booleans in PlatformStyle that hold the |
| 92 | appropriate values for these decisions on the current platform, like: |
| 93 | |
| 94 | static const bool PlatformStyle::kDialogsShouldHaveCloseButton; |
| 95 | |
| 96 | You can then condition your dialog creation code like this: |
| 97 | |
| 98 | if (PlatformStyle::kDialogsShouldHaveCloseButton) |
| 99 | views::Button* close_button = ...; |
| 100 | |
| 101 | TODO(ellyjones): Actually add these variables to PlatformStyle |