Bug 935508 - Implement native theming of <input type=number>. r=roc

This commit is contained in:
Jonathan Watt 2013-12-05 16:20:34 +00:00
Родитель b9e9b31d55
Коммит aa6b5e3313
8 изменённых файлов: 340 добавлений и 23 удалений

Просмотреть файл

@ -3314,6 +3314,8 @@ HTMLInputElement::PreHandleEvent(nsEventChainPreVisitor& aVisitor)
nsNumberControlFrame* numberControlFrame =
do_QueryFrame(GetPrimaryFrame());
if (numberControlFrame) {
bool oldNumberControlSpinTimerSpinsUpValue =
mNumberControlSpinnerSpinsUp;
switch (numberControlFrame->GetSpinButtonForPointerEvent(
aVisitor.mEvent->AsMouseEvent())) {
case nsNumberControlFrame::eSpinButtonUp:
@ -3325,6 +3327,14 @@ HTMLInputElement::PreHandleEvent(nsEventChainPreVisitor& aVisitor)
stopSpin = false;
break;
}
if (mNumberControlSpinnerSpinsUp !=
oldNumberControlSpinTimerSpinsUpValue) {
nsNumberControlFrame* numberControlFrame =
do_QueryFrame(GetPrimaryFrame());
if (numberControlFrame) {
numberControlFrame->SpinnerStateChanged();
}
}
}
if (stopSpin) {
StopNumberControlSpinnerSpin();
@ -3346,6 +3356,15 @@ HTMLInputElement::PreHandleEvent(nsEventChainPreVisitor& aVisitor)
numberControlFrame->HandleFocusEvent(aVisitor.mEvent);
}
}
if (frame->IsThemed()) {
// Our frame's nested <input type=text> will be invalidated when it
// loses focus, but since we are also native themed we need to make
// sure that our entire area is repainted since any focus highlight
// from the theme should be removed from us (the repainting of the
// sub-area occupied by the anon text control is not enough to do
// that).
frame->InvalidateFrame();
}
}
} else if (aVisitor.mEvent->message == NS_KEY_UP) {
WidgetKeyboardEvent* keyEvent = aVisitor.mEvent->AsKeyboardEvent();
@ -3497,6 +3516,12 @@ HTMLInputElement::StartNumberControlSpinnerSpin()
// Capture the mouse so that we can tell if the pointer moves from one
// spin button to the other, or to some other element:
nsIPresShell::SetCapturingContent(this, CAPTURE_IGNOREALLOWED);
nsNumberControlFrame* numberControlFrame =
do_QueryFrame(GetPrimaryFrame());
if (numberControlFrame) {
numberControlFrame->SpinnerStateChanged();
}
}
void
@ -3512,6 +3537,12 @@ HTMLInputElement::StopNumberControlSpinnerSpin()
mNumberControlSpinnerIsSpinning = false;
FireChangeEventIfNeeded();
nsNumberControlFrame* numberControlFrame =
do_QueryFrame(GetPrimaryFrame());
if (numberControlFrame) {
numberControlFrame->SpinnerStateChanged();
}
}
}

Просмотреть файл

@ -688,6 +688,16 @@ public:
*/
static void HandleNumberControlSpin(void* aData);
bool NumberSpinnerUpButtonIsDepressed() const
{
return mNumberControlSpinnerIsSpinning && mNumberControlSpinnerSpinsUp;
}
bool NumberSpinnerDownButtonIsDepressed() const
{
return mNumberControlSpinnerIsSpinning && !mNumberControlSpinnerSpinsUp;
}
bool MozIsTextField(bool aExcludePassword);
nsIEditor* GetEditor();

Просмотреть файл

@ -318,6 +318,49 @@ nsNumberControlFrame::GetAnonTextControl()
return mTextField ? HTMLInputElement::FromContent(mTextField) : nullptr;
}
/* static */ nsNumberControlFrame*
nsNumberControlFrame::GetNumberControlFrameForTextField(nsIFrame* aFrame)
{
// If aFrame is the anon text field for an <input type=number> then we expect
// the frame of its mContent's grandparent to be that input's frame. We
// have to check for this via the content tree because we don't know whether
// extra frames will be wrapped around any of the elements between aFrame and
// the nsNumberControlFrame that we're looking for (e.g. flex wrappers).
nsIContent* content = aFrame->GetContent();
if (content->IsInNativeAnonymousSubtree() &&
content->GetParent() && content->GetParent()->GetParent()) {
nsIContent* grandparent = content->GetParent()->GetParent();
if (grandparent->IsHTML(nsGkAtoms::input) &&
grandparent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
nsGkAtoms::number, eCaseMatters)) {
return do_QueryFrame(grandparent->GetPrimaryFrame());
}
}
return nullptr;
}
/* static */ nsNumberControlFrame*
nsNumberControlFrame::GetNumberControlFrameForSpinButton(nsIFrame* aFrame)
{
// If aFrame is a spin button for an <input type=number> then we expect the
// frame of its mContent's great-grandparent to be that input's frame. We
// have to check for this via the content tree because we don't know whether
// extra frames will be wrapped around any of the elements between aFrame and
// the nsNumberControlFrame that we're looking for (e.g. flex wrappers).
nsIContent* content = aFrame->GetContent();
if (content->IsInNativeAnonymousSubtree() &&
content->GetParent() && content->GetParent()->GetParent() &&
content->GetParent()->GetParent()->GetParent()) {
nsIContent* greatgrandparent = content->GetParent()->GetParent()->GetParent();
if (greatgrandparent->IsHTML(nsGkAtoms::input) &&
greatgrandparent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
nsGkAtoms::number, eCaseMatters)) {
return do_QueryFrame(greatgrandparent->GetPrimaryFrame());
}
}
return nullptr;
}
int32_t
nsNumberControlFrame::GetSpinButtonForPointerEvent(WidgetGUIEvent* aEvent) const
{
@ -330,9 +373,64 @@ nsNumberControlFrame::GetSpinButtonForPointerEvent(WidgetGUIEvent* aEvent) const
if (aEvent->originalTarget == mSpinDown) {
return eSpinButtonDown;
}
if (aEvent->originalTarget == mSpinBox) {
// In the case that the up/down buttons are hidden (display:none) we use
// just the spin box element, spinning up if the pointer is over the top
// half of the element, or down if it's over the bottom half. This is
// important to handle since this is the state things are in for the
// default UA style sheet. See the comment in forms.css for why.
LayoutDeviceIntPoint absPoint = aEvent->refPoint;
nsPoint point =
nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent,
LayoutDeviceIntPoint::ToUntyped(absPoint),
mSpinBox->GetPrimaryFrame());
if (point != nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE)) {
if (point.y < mSpinBox->GetPrimaryFrame()->GetSize().height / 2) {
return eSpinButtonUp;
}
return eSpinButtonDown;
}
}
return eSpinButtonNone;
}
void
nsNumberControlFrame::SpinnerStateChanged() const
{
nsIFrame* spinUpFrame = mSpinUp->GetPrimaryFrame();
if (spinUpFrame && spinUpFrame->IsThemed()) {
spinUpFrame->InvalidateFrame();
}
nsIFrame* spinDownFrame = mSpinDown->GetPrimaryFrame();
if (spinDownFrame && spinDownFrame->IsThemed()) {
spinDownFrame->InvalidateFrame();
}
}
bool
nsNumberControlFrame::SpinnerUpButtonIsDepressed() const
{
return HTMLInputElement::FromContent(mContent)->
NumberSpinnerUpButtonIsDepressed();
}
bool
nsNumberControlFrame::SpinnerDownButtonIsDepressed() const
{
return HTMLInputElement::FromContent(mContent)->
NumberSpinnerDownButtonIsDepressed();
}
bool
nsNumberControlFrame::IsFocused() const
{
// Normally this depends on the state of our anonymous text control (which
// takes focus for us), but in the case that it does not have a frame we will
// have focus ourself.
return mTextField->AsElement()->State().HasState(NS_EVENT_STATE_FOCUS) ||
mContent->AsElement()->State().HasState(NS_EVENT_STATE_FOCUS);
}
void
nsNumberControlFrame::HandleFocusEvent(WidgetEvent* aEvent)
{
@ -342,6 +440,27 @@ nsNumberControlFrame::HandleFocusEvent(WidgetEvent* aEvent)
}
}
#define STYLES_DISABLING_NATIVE_THEMING \
NS_AUTHOR_SPECIFIED_BACKGROUND | \
NS_AUTHOR_SPECIFIED_PADDING | \
NS_AUTHOR_SPECIFIED_BORDER
bool
nsNumberControlFrame::ShouldUseNativeStyleForSpinner() const
{
nsIFrame* spinUpFrame = mSpinUp->GetPrimaryFrame();
nsIFrame* spinDownFrame = mSpinDown->GetPrimaryFrame();
return spinUpFrame &&
spinUpFrame->StyleDisplay()->mAppearance == NS_THEME_SPINNER_UP_BUTTON &&
!PresContext()->HasAuthorSpecifiedRules(spinUpFrame,
STYLES_DISABLING_NATIVE_THEMING) &&
spinDownFrame &&
spinDownFrame->StyleDisplay()->mAppearance == NS_THEME_SPINNER_DOWN_BUTTON &&
!PresContext()->HasAuthorSpecifiedRules(spinDownFrame,
STYLES_DISABLING_NATIVE_THEMING);
}
void
nsNumberControlFrame::AppendAnonymousContentTo(nsBaseContentList& aElements,
uint32_t aFilter)

Просмотреть файл

@ -44,7 +44,7 @@ public:
virtual void DestroyFrom(nsIFrame* aDestructRoot) MOZ_OVERRIDE;
virtual bool IsLeaf() const MOZ_OVERRIDE { return true; }
virtual bool IsLeaf() const MOZ_OVERRIDE { return false; }
NS_IMETHOD Reflow(nsPresContext* aPresContext,
nsHTMLReflowMetrics& aDesiredSize,
@ -91,6 +91,18 @@ public:
HTMLInputElement* GetAnonTextControl();
/**
* If the frame is the frame for an nsNumberControlFrame's anonymous text
* field, returns the nsNumberControlFrame. Else returns nullptr.
*/
static nsNumberControlFrame* GetNumberControlFrameForTextField(nsIFrame* aFrame);
/**
* If the frame is the frame for an nsNumberControlFrame's up or down spin
* button, returns the nsNumberControlFrame. Else returns nullptr.
*/
static nsNumberControlFrame* GetNumberControlFrameForSpinButton(nsIFrame* aFrame);
enum SpinButtonEnum {
eSpinButtonNone,
eSpinButtonUp,
@ -104,10 +116,19 @@ public:
*/
int32_t GetSpinButtonForPointerEvent(WidgetGUIEvent* aEvent) const;
void SpinnerStateChanged() const;
bool SpinnerUpButtonIsDepressed() const;
bool SpinnerDownButtonIsDepressed() const;
bool IsFocused() const;
void HandleFocusEvent(WidgetEvent* aEvent);
virtual Element* GetPseudoElement(nsCSSPseudoElements::Type aType) MOZ_OVERRIDE;
bool ShouldUseNativeStyleForSpinner() const;
private:
nsresult MakeAnonymousElement(Element** aResult,

Просмотреть файл

@ -897,9 +897,6 @@ input[type=number]::-moz-number-wrapper {
display: flex;
float: none !important;
position: static !important;
-moz-box-sizing: border-box;
width: 100%;
height: 100%;
}
input[type=number]::-moz-number-text {
@ -922,18 +919,23 @@ input[type=number]::-moz-number-text {
input[type=number]::-moz-number-spin-box {
display: flex;
flex-direction: column;
flex: 0 8px;
cursor: default;
padding: 1px;
%ifdef XP_WIN
/* The Window's Theme's spin buttons have a very narrow minimum width, so
* make it something reasonable:
*/
width: 16px;
%endif
height: 0;
align-self: center;
justify-content: center;
}
input[type=number]::-moz-number-spin-up {
/* We should be "display:block" so that we don't get wrapped in an anonymous
* flex item that will prevent the setting of the "flex" property below from
* working.
*/
display: block;
flex: 1;
-moz-appearance: spinner-upbutton;
display: block; /* bug 926670 */
flex: none;
cursor: default;
/* Style for when native theming is off: */
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="6" height="5"><path d="M1,4 L3,0 5,4" fill="dimgrey"/></svg>');
background-repeat: no-repeat;
background-position: center bottom;
@ -944,12 +946,11 @@ input[type=number]::-moz-number-spin-up {
}
input[type=number]::-moz-number-spin-down {
/* We should be "display:block" so that we don't get wrapped in an anonymous
* flex item that will prevent the setting of the "flex" property below from
* working.
*/
display: block;
flex: 1;
-moz-appearance: spinner-downbutton;
display: block; /* bug 926670 */
flex: none;
cursor: default;
/* Style for when native theming is off: */
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="6" height="5"><path d="M1,1 L3,5 5,1" fill="dimgrey"/></svg>');
background-repeat: no-repeat;
background-position: center top;

Просмотреть файл

@ -104,6 +104,10 @@ protected:
const HIRect& inBoxRect, ThemeDrawState inDrawState,
ThemeButtonAdornment inAdornment, nsEventStates inState,
nsIFrame* aFrame);
void DrawSpinButton(CGContextRef context, ThemeButtonKind inKind,
const HIRect& inBoxRect, ThemeDrawState inDrawState,
ThemeButtonAdornment inAdornment, nsEventStates inState,
nsIFrame* aFrame, uint8_t aWidgetType);
void DrawUnifiedToolbar(CGContextRef cgContext, const HIRect& inBoxRect,
NSWindow* aWindow);
void DrawStatusBar(CGContextRef cgContext, const HIRect& inBoxRect,

Просмотреть файл

@ -5,6 +5,7 @@
#include "nsNativeThemeCocoa.h"
#include "nsObjCExceptions.h"
#include "nsNumberControlFrame.h"
#include "nsRangeFrame.h"
#include "nsRenderingContext.h"
#include "nsRect.h"
@ -1210,6 +1211,26 @@ nsNativeThemeCocoa::DrawDropdown(CGContextRef cgContext, const HIRect& inBoxRect
NS_OBJC_END_TRY_ABORT_BLOCK;
}
static const CellRenderSettings spinnerSettings = {
{
NSMakeSize(11, 16), // mini (width trimmed by 2px to reduce blank border)
NSMakeSize(15, 22), // small
NSMakeSize(19, 27) // regular
},
{
NSMakeSize(11, 16), // mini (width trimmed by 2px to reduce blank border)
NSMakeSize(15, 22), // small
NSMakeSize(19, 27) // regular
},
{
{ // Leopard
{0, 0, 0, 0}, // mini
{0, 0, 0, 0}, // small
{0, 0, 0, 0} // regular
}
}
};
void
nsNativeThemeCocoa::DrawSpinButtons(CGContextRef cgContext, ThemeButtonKind inKind,
const HIRect& inBoxRect, ThemeDrawState inDrawState,
@ -1234,6 +1255,56 @@ nsNativeThemeCocoa::DrawSpinButtons(CGContextRef cgContext, ThemeButtonKind inKi
NS_OBJC_END_TRY_ABORT_BLOCK;
}
void
nsNativeThemeCocoa::DrawSpinButton(CGContextRef cgContext,
ThemeButtonKind inKind,
const HIRect& inBoxRect,
ThemeDrawState inDrawState,
ThemeButtonAdornment inAdornment,
nsEventStates inState,
nsIFrame* aFrame,
uint8_t aWidgetType)
{
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
MOZ_ASSERT(aWidgetType == NS_THEME_SPINNER_UP_BUTTON ||
aWidgetType == NS_THEME_SPINNER_DOWN_BUTTON);
HIThemeButtonDrawInfo bdi;
bdi.version = 0;
bdi.kind = inKind;
bdi.value = kThemeButtonOff;
bdi.adornment = inAdornment;
if (IsDisabled(aFrame, inState))
bdi.state = kThemeStateUnavailable;
else
bdi.state = FrameIsInActiveWindow(aFrame) ? inDrawState : kThemeStateActive;
// Cocoa only allows kThemeIncDecButton to paint the up and down spin buttons
// together as a single unit (presumably because when one button is active,
// the appearance of both changes (in different ways)). Here we have to paint
// both buttons, using clip to hide the one we don't want to paint.
HIRect drawRect = inBoxRect;
drawRect.size.height *= 2;
if (aWidgetType == NS_THEME_SPINNER_DOWN_BUTTON) {
drawRect.origin.y -= inBoxRect.size.height;
}
// Shift the drawing a little to the left, since cocoa paints with more
// blank space around the visual buttons than we'd like:
drawRect.origin.x -= 1;
CGContextSaveGState(cgContext);
CGContextClipToRect(cgContext, inBoxRect);
HIThemeDrawButton(&drawRect, &bdi, cgContext, HITHEME_ORIENTATION, NULL);
CGContextRestoreGState(cgContext);
NS_OBJC_END_TRY_ABORT_BLOCK;
}
void
nsNativeThemeCocoa::DrawFrame(CGContextRef cgContext, HIThemeFrameKind inKind,
const HIRect& inBoxRect, bool inDisabled,
@ -2135,8 +2206,14 @@ nsNativeThemeCocoa::DrawWidgetBackground(nsRenderingContext* aContext,
break;
case NS_THEME_SPINNER: {
ThemeDrawState state = kThemeStateActive;
nsIContent* content = aFrame->GetContent();
if (content->IsHTML()) {
// In HTML the theming for the spin buttons is drawn individually into
// their own backgrounds instead of being drawn into the background of
// their spinner parent as it is for XUL.
break;
}
ThemeDrawState state = kThemeStateActive;
if (content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::state,
NS_LITERAL_STRING("up"), eCaseMatters)) {
state = kThemeStatePressedUp;
@ -2151,6 +2228,23 @@ nsNativeThemeCocoa::DrawWidgetBackground(nsRenderingContext* aContext,
}
break;
case NS_THEME_SPINNER_UP_BUTTON:
case NS_THEME_SPINNER_DOWN_BUTTON: {
nsNumberControlFrame* numberControlFrame =
nsNumberControlFrame::GetNumberControlFrameForSpinButton(aFrame);
if (numberControlFrame) {
ThemeDrawState state = kThemeStateActive;
if (numberControlFrame->SpinnerUpButtonIsDepressed()) {
state = kThemeStatePressedUp;
} else if (numberControlFrame->SpinnerDownButtonIsDepressed()) {
state = kThemeStatePressedDown;
}
DrawSpinButton(cgContext, kThemeIncDecButtonMini, macRect, state,
kThemeAdornmentNone, eventState, aFrame, aWidgetType);
}
}
break;
case NS_THEME_TOOLBAR_BUTTON:
DrawSegment(cgContext, macRect, eventState, aFrame, toolbarButtonRenderSettings);
break;
@ -2747,12 +2841,25 @@ nsNativeThemeCocoa::GetMinimumWidgetSize(nsRenderingContext* aContext,
}
case NS_THEME_SPINNER:
case NS_THEME_SPINNER_UP_BUTTON:
case NS_THEME_SPINNER_DOWN_BUTTON:
{
SInt32 buttonHeight = 0, buttonWidth = 0;
::GetThemeMetric(kThemeMetricLittleArrowsWidth, &buttonWidth);
::GetThemeMetric(kThemeMetricLittleArrowsHeight, &buttonHeight);
if (aFrame->GetContent()->IsXUL()) {
::GetThemeMetric(kThemeMetricLittleArrowsWidth, &buttonWidth);
::GetThemeMetric(kThemeMetricLittleArrowsHeight, &buttonHeight);
} else {
NSSize size =
spinnerSettings.minimumSizes[EnumSizeForCocoaSize(NSMiniControlSize)];
buttonWidth = size.width;
buttonHeight = size.height;
if (aWidgetType != NS_THEME_SPINNER) {
// the buttons are half the height of the spinner
buttonHeight /= 2;
}
}
aResult->SizeTo(buttonWidth, buttonHeight);
*aIsOverridable = false;
*aIsOverridable = true;
break;
}
@ -3100,6 +3207,8 @@ nsNativeThemeCocoa::ThemeSupportsWidget(nsPresContext* aPresContext, nsIFrame* a
case NS_THEME_BUTTON_BEVEL:
case NS_THEME_TOOLBAR_BUTTON:
case NS_THEME_SPINNER:
case NS_THEME_SPINNER_UP_BUTTON:
case NS_THEME_SPINNER_DOWN_BUTTON:
case NS_THEME_TOOLBAR:
case NS_THEME_MOZ_MAC_UNIFIED_TOOLBAR:
case NS_THEME_STATUSBAR:
@ -3224,6 +3333,8 @@ nsNativeThemeCocoa::WidgetAppearanceDependsOnWindowFocus(uint8_t aWidgetType)
case NS_THEME_MENUSEPARATOR:
case NS_THEME_TOOLTIP:
case NS_THEME_SPINNER:
case NS_THEME_SPINNER_UP_BUTTON:
case NS_THEME_SPINNER_DOWN_BUTTON:
case NS_THEME_TOOLBAR_SEPARATOR:
case NS_THEME_TOOLBOX:
case NS_THEME_TEXTFIELD:

Просмотреть файл

@ -9,6 +9,7 @@
#include "nsIContent.h"
#include "nsIFrame.h"
#include "nsIPresShell.h"
#include "nsNumberControlFrame.h"
#include "nsPresContext.h"
#include "nsEventStateManager.h"
#include "nsString.h"
@ -74,6 +75,16 @@ nsNativeTheme::GetContentState(nsIFrame* aFrame, uint8_t aWidgetType)
nsEventStates flags;
if (frameContent->IsElement()) {
flags = frameContent->AsElement()->State();
// <input type=number> needs special handling since its nested native
// anonymous <input type=text> takes focus for it.
if (aWidgetType == NS_THEME_TEXTFIELD &&
frameContent->IsHTML(nsGkAtoms::input)) {
nsNumberControlFrame *numberControlFrame = do_QueryFrame(aFrame);
if (numberControlFrame && numberControlFrame->IsFocused()) {
flags |= NS_EVENT_STATE_FOCUS;
}
}
}
if (isXULCheckboxRadio && aWidgetType == NS_THEME_RADIO) {
@ -313,6 +324,15 @@ nsNativeTheme::IsWidgetStyled(nsPresContext* aPresContext, nsIFrame* aFrame,
}
}
if (aWidgetType == NS_THEME_SPINNER_UP_BUTTON ||
aWidgetType == NS_THEME_SPINNER_DOWN_BUTTON) {
nsNumberControlFrame* numberControlFrame =
nsNumberControlFrame::GetNumberControlFrameForSpinButton(aFrame);
if (numberControlFrame) {
return !numberControlFrame->ShouldUseNativeStyleForSpinner();
}
}
return (aWidgetType == NS_THEME_BUTTON ||
aWidgetType == NS_THEME_TEXTFIELD ||
aWidgetType == NS_THEME_TEXTFIELD_MULTILINE ||