principles-ui-components
Version:
Supporting UI controller for Tizen TV web application, which developed base on React Framework.
525 lines (442 loc) • 18 kB
JavaScript
/**
* @author Ma Cong(cong001.ma@samsung.com)
* @fileoverview This module manages Button item.
* @date 2017/08/07 (last modified date)
*
* Copyright 2017 by Samsung Electronics, Inc.,
*
* This software is the confidential and proprietary information
* of Samsung Electronics, Inc. ("Confidential Information"). You
* shall not disclose such Confidential Information and shall use
* it only in accordance with the terms of the license agreement
* you entered into with Samsung.
*/
import React, { Component, PropTypes } from 'react';
import { is, Map, fromJS } from 'immutable';
import { MAIN_TEXT_FONT, TextItemFamily, AnimationEffect, KEY, TTSTYPE } from './common/CommonDefine';
import './css/Button.css';
import CommonAPI from './common/CommonAPI';
import PressFeedback from './common/PressFeedback';
import ScrollText from './common/ScrollText';
import Image from './common/Image';
import TTS from './common/TTS';
// Define Button Type
export const BUTTON_TYPE = {
ICON_BUTTON: 'icon',
TEXT_BUTTON: 'text',
DROP_DOWN_TEXT_BUTTON: 'dropdown_text',
};
const btnResource = {
normal: 'url(./images/btn/c_basic_button_white_bg_normal_9patch.png) 5 4 fill stretch',
highContrast: 'url(./images/btn/c_basic_button_highcontrast_bg_normal_9patch.png) 6 5 fill stretch',
focus: 'url(./images/btn/r_highlight_bg_focus_9patch.png) 20 14 fill stretch',
select: 'url(./images/btn/c_basic_button_white_bg_select_9patch.png) 5 4 fill stretch',
};
export default class Button extends Component {
constructor(props) {
super(props);
this.state = {
hasFocus: false,
};
this.animationflag = false;
this.animationDone = this.animationDone.bind(this);
this.mainTextFont = MAIN_TEXT_FONT;
this.mainTextSize = TextItemFamily.TITLE2_TEXT_SIZE;
this.bodyGap = TextItemFamily.BODY_TEXT_GAP;
}
componentDidMount() {
console.log('Button componentDidMount');
const { ttsEnable } = this.props;
if (ttsEnable) {
this.playTTS();
}
}
componentWillReceiveProps(nextProps) {
}
/* shouldComponentUpdate(nextProps, nextState) { // return true -->render()
return !(is(fromJS(nextProps), fromJS(this.props))) || !(is(fromJS(nextState), fromJS(this.state)));
}*/
componentDidUpdate() {
console.log('Button componentDidUpdate');
const { ttsEnable } = this.props;
if (ttsEnable) {
this.playTTS();
}
}
componentWillUnmount() {
}
getElementPos(elem) {
console.log('Button.getElementPos IN');
const { buttonW } = this.props;
let x = 0;
let y = 0;
let mElem = elem;
while (mElem != null) {
x += mElem.offsetLeft;
y += mElem.offsetTop;
mElem = mElem.offsetParent;
}
x += buttonW / 2;
console.log(`getElementPos x:${x}`);
console.log(`getElementPos y:${y}`);
return { x, y };
}
getButtonStyle() {
const { focus, buttonScale, buttonType } = this.props;
const ButtonStyle = {};
if ((focus === true || this.state.hasFocus === true) && buttonScale !== 1) {
ButtonStyle.animationName = 'doScale';
ButtonStyle.animationDuration = '1.1s';
ButtonStyle.transform = `scale(${buttonScale},${buttonScale}) translateZ(100px)`;
ButtonStyle.willChange = 'transform';
ButtonStyle.animationTimingFunction = AnimationEffect.Elastic;
} else if ((focus === false || this.state.hasFocus === false) && buttonScale !== 1) {
ButtonStyle.animationName = 'doScaleBack';
if (buttonType !== BUTTON_TYPE.ICON_BUTTON) {
ButtonStyle.animationDuration = '0.85s';
ButtonStyle.animationTimingFunction = AnimationEffect.out;
}
}
return ButtonStyle;
}
getEnlargeButtonStyle() {
const { highContrast, enlarge, focus } = this.props;
const ButtonStyle = {};
if (focus === true || this.state.hasFocus === true) {
ButtonStyle.animationName = 'doScaleEnlargeFocus';
ButtonStyle.animationDuration = '0.85s';
ButtonStyle.transform = 'scale(1.296,1.296) translateZ(100px)';
ButtonStyle.willChange = 'transform';
ButtonStyle.animationTimingFunction = AnimationEffect.Elastic;
} else if (focus === false || this.state.hasFocus === false) {
ButtonStyle.animationName = 'doScaleEnlarge';
ButtonStyle.animationDuration = '0.85s';
ButtonStyle.transform = 'scale(1.2,1.2) translateZ(100px)';
ButtonStyle.willChange = 'transform';
ButtonStyle.animationTimingFunction = AnimationEffect.Elastic;
}
return ButtonStyle;
}
animationDone(e) {
this.animationflag = true;
e.preventDefault();
const target = e.target;
if (target === e.currentTarget) {
if (e.type === 'animationend') {
if (this.props.clickCB && typeof this.props.clickCB === 'function') {
this.props.clickCB();
}
}
}
}
calculateTextW() {
const { message } = this.props;
let textWidth = 0;
if (message) {
textWidth = CommonAPI.getTextWidth(message, this.mainTextSize, this.mainTextFont);
}
console.log(`textWidth=${textWidth}`);
return textWidth;
}
keyFocusOut() {
console.log('[Button.js::keyFocusOut()]');
this.setState({ hasFocus: false });
return this.getElementPos(this.motionContent);
}
keyFocusIn(position) {
console.log('[Button.js::keyFocusIn()] ');
this.setState({ hasFocus: true });
}
/**
* voice guide play
* @name playTTS
* @method
* @param
* @memberof
*/
playTTS() {
console.log('[Button.js::playTTS()] ');
if (this.props.focus || this.state.hasFocus) {
this.TTSnode.playTTS();
}
}
procIconBtn() {
const { iconOSD, clickCB, highContrast, enlarge, disabled, focus, selected, bgcolor, bgimg, ttsEnable, ttsText } = this.props;
const { buttonW, buttonH } = this.props;
const ImgStyle = {};
const ButtonStyle = enlarge ? this.getEnlargeButtonStyle() : this.getButtonStyle();
let imageOSD = {
l: iconOSD.l,
t: iconOSD.t,
w: iconOSD.w,
h: iconOSD.h,
url: iconOSD.url_n,
};
console.log(`selected:${selected}`);
if (buttonW && buttonH) {
ButtonStyle.width = buttonW;
ButtonStyle.height = buttonH;
}
ButtonStyle.backgroundColor = bgcolor; // for vodpage set icon bg
ButtonStyle.borderImage = highContrast ? btnResource.highContrast : bgimg;
// disabled
if (disabled) {
ImgStyle.opacity = '0.2';
}
// focus
if ((focus === true || this.state.hasFocus === true) && iconOSD.url_f) {
imageOSD = {
l: iconOSD.l,
t: iconOSD.t,
w: iconOSD.w,
h: iconOSD.h,
url: iconOSD.url_f,
};
}
// TTS
let TTSComponent = null;
if (ttsEnable) {
TTSComponent = <TTS ref={(TTSnode) => { this.TTSnode = TTSnode; }} ttsEnable={ttsEnable} ttsText={ttsText} />;
}
return (
<div className='iconArea'>
<PressFeedback start={selected} animationDoneCB={this.props.clickCB}>
<div ref={(motionContent) => { this.motionContent = motionContent; }} className='image-bg' style={ButtonStyle}>
<div style={ImgStyle}>
<Image OSD={imageOSD} opacity={0} />
</div>
</div>
</PressFeedback>
{TTSComponent}
</div>
);
}
procTextBtn() {
const { message, highContrast, enlarge, clickCB, disabled, focus, selected, ttsEnable, ttsText } = this.props;
const { buttonW, buttonH } = this.props;
let textStyle = {};
let marqueeFlag = false;
this.textWidth = this.calculateTextW();
// highContrast & enlarge
const ButtonStyle = enlarge ? this.getEnlargeButtonStyle() : this.getButtonStyle();
const BtnText = highContrast ? 'btn-text-highContrast' : 'btn-text';
ButtonStyle.border = '2px solid rgb(255,255,255)';
ButtonStyle.borderImage = highContrast ? btnResource.highContrast : btnResource.normal;
if (focus === true || this.state.hasFocus === true) {
textStyle.color = '#000000';
ButtonStyle.borderImage = btnResource.focus;
}
if (buttonW && buttonH) {
ButtonStyle.width = buttonW;
ButtonStyle.height = buttonH;
}
console.log(`ButtonStyle.width=${ButtonStyle.width}`);
textStyle = {
left: 21,
width: ButtonStyle.width - 42,
height: buttonH,
lineHeight: buttonH,
};
// disabled
if (disabled) {
textStyle.opacity = '0.2';
}
if (this.textWidth > textStyle.width && (focus || this.state.hasFocus)) {
marqueeFlag = true;
}
const aligntext = marqueeFlag ? 'left' : 'center';
// TTS
let TTSComponent = null;
if (ttsEnable) {
TTSComponent = <TTS ref={(TTSnode) => { this.TTSnode = TTSnode; }} ttsEnable={ttsEnable} ttsText={ttsText} />;
}
return (
<div style={{ position: 'relative' }}>
<PressFeedback start={selected} animationDoneCB={this.props.clickCB}>
<div ref={(motionContent) => { this.motionContent = motionContent; }} className='button-ani' style={ButtonStyle}>
<div className={BtnText} style={textStyle}>
<ScrollText
scroll={marqueeFlag}
fontSize={this.mainTextSize}
fontFamily={this.mainTextFont}
textGap={this.bodyGap}
width={textStyle.width}
height={textStyle.height}
lineHeight={`${textStyle.height}px`}
textAlign={aligntext}
>
{message}
</ScrollText>
</div>
</div>
</PressFeedback>
{TTSComponent}
</div>
);
}
procDropdowntextBtn() {
const { message, highContrast, enlarge, clickCB, disabled, focus, selected, ttsEnable, ttsText } = this.props;
const { buttonW, buttonH } = this.props;
const textStyle = {};
const iconStyle = {};
let marqueeFlag = false;
this.textWidth = this.calculateTextW();
// highContrast & enlarge
const ButtonStyle = enlarge ? this.getEnlargeButtonStyle() : this.getButtonStyle();
const BtnText = highContrast ? 'btn-text-highContrast' : 'text-icon-title';
if (buttonW && buttonH) {
ButtonStyle.width = buttonW;
ButtonStyle.height = buttonH;
}
ButtonStyle.border = '2px solid rgb(255,255,255)';
ButtonStyle.borderImage = highContrast ? btnResource.highContrast : btnResource.normal;
if ((focus || this.state.hasFocus)) {
iconStyle.backgroundImage = 'url(./images/btn/c_buttondropdown_arrow_down_focus.png)';
} else {
iconStyle.backgroundImage = 'url(./images/btn/c_buttondropdown_arrow_down.png)';
}
if (focus === true || this.state.hasFocus === true) {
textStyle.color = '#000000';
ButtonStyle.borderImage = btnResource.focus;
}
const elem = this.motionContent;
if (this.animationflag) {
this.animationflag = false;
elem.removeEventListener('animationend', this.animationDone, false);
}
if (selected && !enlarge) {
ButtonStyle.animationName = 'doScaleBack';
ButtonStyle.animationDuration = '0.85s';
ButtonStyle.transform = 'scale(1,1) translateZ(100px)';
ButtonStyle.animationTimingFunction = AnimationEffect.Out;
ButtonStyle.borderImage = btnResource.select;
elem.addEventListener('animationend', this.animationDone, false);
iconStyle.backgroundImage = 'url(./images/btn/c_buttondropdown_arrow_up_focus.png)';
} else if (selected && enlarge) {
ButtonStyle.animationName = 'doScaleEnlarge';
ButtonStyle.animationDuration = '0.85s';
ButtonStyle.transform = 'scale(1.2,1.2) translateZ(100px)';
ButtonStyle.animationTimingFunction = AnimationEffect.Out;
ButtonStyle.borderImage = btnResource.select;
elem.addEventListener('animationend', this.animationDone, false);
iconStyle.backgroundImage = 'url(./images/btn/c_buttondropdown_arrow_up_focus.png)';
} else if (!selected && (focus || this.state.hasFocus) && !enlarge) {
ButtonStyle.animationName = 'doScale';
ButtonStyle.animationDuration = '0.85s';
ButtonStyle.transform = 'scale(1.08,1.08) translateZ(100px)';
ButtonStyle.willChange = 'transform';
ButtonStyle.animationTimingFunction = AnimationEffect.Out;
}
textStyle.width = ButtonStyle.width - 126;
textStyle.height = buttonH;
iconStyle.left = textStyle.width + 82;
const barStyle = {
position: 'absolute',
zIndex: 1000,
left: textStyle.width + 50,
top: (ButtonStyle.height - 64) / 2,
width: '2px',
height: '64px',
backgroundImage: 'url(images/btn/c_buttondropdown_bar.png)',
};
// for scrollText
if (this.textWidth > textStyle.width && (focus || this.state.hasFocus) && !selected) {
marqueeFlag = true;
}
// disabled
if (disabled) {
textStyle.opacity = '0.4';
}
const aligntext = marqueeFlag ? 'left' : 'center';
// TTS
let TTSComponent = null;
if (ttsEnable) {
TTSComponent = <TTS ref={(TTSnode) => { this.TTSnode = TTSnode; }} ttsEnable={ttsEnable} ttsText={ttsText} />;
}
return (
<div>
<div ref={(motionContent) => { this.motionContent = motionContent; }} className='text-image-bg' style={ButtonStyle} >
<div className={BtnText} style={textStyle} >
<ScrollText
scroll={marqueeFlag}
fontSize={this.mainTextSize}
fontFamily={this.mainTextFont}
textGap={this.bodyGap}
width={textStyle.width}
height={textStyle.height}
lineHeight={`${textStyle.height}px`}
textAlign={aligntext}
>
{message}
</ScrollText>
</div>
<div style={barStyle} />
<div className='text-icon-img' style={iconStyle} />
</div>
{TTSComponent}
</div>
);
}
render() {
console.log('[Button.js::render()]');
const { buttonType } = this.props;
switch (buttonType) {
case BUTTON_TYPE.ICON_BUTTON:
return this.procIconBtn();
case BUTTON_TYPE.TEXT_BUTTON:
return this.procTextBtn();
case BUTTON_TYPE.DROP_DOWN_TEXT_BUTTON:
return this.procDropdowntextBtn();
default:
return (<div />);
}
}
}
Button.defaultProps = {
focus: false,
buttonW: 414,
buttonH: 84,
highContrast: false,
enlarge: false,
disabled: false,
message: '',
selected: false,
clickCB: null,
buttonScale: 1.08,
bgcolor: null,
bgimg: null,
ttsEnable: false,
ttsText: '',
iconOSD: {
l: 22,
t: 23,
w: 38,
h: 38,
url_n: '',
url_f: '',
},
};
Button.propTypes = {
focus: PropTypes.bool,
buttonType: PropTypes.string.isRequired, // The type of button, "ICON_BUTTON" means only icon, "TEXT_BUTTON" means only text, "DROP_DOWN_BUTTON" means dropdown button(for text)
clickCB: PropTypes.func, // button call back func
message: PropTypes.string.isRequired, // message of button show
highContrast: PropTypes.bool,
enlarge: PropTypes.bool,
disabled: PropTypes.bool,
selected: PropTypes.bool,
buttonW: PropTypes.number, // button Width, if null, will calculate width according to rule
buttonH: PropTypes.number, // button Height
bgcolor: PropTypes.string,
bgimg: PropTypes.string,
ttsEnable: PropTypes.bool,
ttsText: PropTypes.string,
iconOSD: PropTypes.shape({
l: PropTypes.number,
t: PropTypes.number,
w: PropTypes.number,
h: PropTypes.number,
url_n: PropTypes.string.isRequired,
url_f: PropTypes.string,
}),
};