diff options
Diffstat (limited to 'gui')
| -rw-r--r-- | gui/src/renderer/components/AppButton.tsx | 2 | ||||
| -rw-r--r-- | gui/src/renderer/components/Marquee.tsx | 83 | ||||
| -rw-r--r-- | gui/src/renderer/components/TunnelControl.tsx | 15 |
3 files changed, 93 insertions, 7 deletions
diff --git a/gui/src/renderer/components/AppButton.tsx b/gui/src/renderer/components/AppButton.tsx index 9f6fe860b3..212e5cb1a3 100644 --- a/gui/src/renderer/components/AppButton.tsx +++ b/gui/src/renderer/components/AppButton.tsx @@ -84,7 +84,7 @@ interface IState { textAdjustment: number; } -export class BaseButton extends Component<IProps, IState> { +class BaseButton extends Component<IProps, IState> { public state: IState = { hovered: false, textAdjustment: 0, diff --git a/gui/src/renderer/components/Marquee.tsx b/gui/src/renderer/components/Marquee.tsx new file mode 100644 index 0000000000..dc019d74ca --- /dev/null +++ b/gui/src/renderer/components/Marquee.tsx @@ -0,0 +1,83 @@ +import * as React from 'react'; +import { Animated, Component, Styles, Types, UserInterface, View } from 'reactxp'; + +const styles = { + text: Styles.createTextStyle({ + // @ts-ignore + width: 'fit-content', + whiteSpace: 'nowrap', + }), +}; + +interface IMarqueeProps { + style?: Types.StyleRuleSetRecursive<Types.ButtonStyleRuleSet>; +} + +export default class Marquee extends Component<IMarqueeProps> { + private initialLeft = Animated.createValue(0.0); + private textAnimation = Styles.createAnimatedTextStyle({ left: this.initialLeft }); + private textRef = React.createRef<Animated.Text>(); + + private animationTimeout?: number; + private animation?: Types.Animated.CompositeAnimation; + + public componentDidMount() { + this.startAnimation(); + } + + public componentDidUpdate() { + this.startAnimation(); + } + + public componentWillUnmount() { + this.stopAnimation(); + } + + public render() { + return ( + <View> + <Animated.Text + ref={this.textRef} + style={[styles.text, this.textAnimation, this.props.style]}> + {this.props.children} + </Animated.Text> + </View> + ); + } + + private async startAnimation() { + this.stopAnimation(); + + this.animationTimeout = setTimeout(async () => { + if (this.textRef.current) { + const textLayout = await UserInterface.measureLayoutRelativeToWindow(this.textRef.current); + const viewLayout = await UserInterface.measureLayoutRelativeToWindow(this); + this.startAnimationImpl(textLayout.width - viewLayout.width, false); + } + }, 1000); + } + + private startAnimationImpl(length: number, reverse: boolean) { + if (length >= 0) { + this.animation = Animated.timing(this.initialLeft, { + toValue: reverse ? 0.0 : -length, + duration: length * 80, + delay: 2000, + easing: Animated.Easing.Linear(), + }); + + this.animation.start(({ finished }) => { + if (finished) { + this.startAnimationImpl(length, !reverse); + } + }); + } + } + + private stopAnimation() { + clearTimeout(this.animationTimeout); + if (this.animation) { + this.animation.stop(); + } + } +} diff --git a/gui/src/renderer/components/TunnelControl.tsx b/gui/src/renderer/components/TunnelControl.tsx index eba2a3190b..47310e2b84 100644 --- a/gui/src/renderer/components/TunnelControl.tsx +++ b/gui/src/renderer/components/TunnelControl.tsx @@ -1,11 +1,12 @@ import * as React from 'react'; -import { Component, Styles, Text, Types, View } from 'reactxp'; +import { Component, Styles, Types, View } from 'reactxp'; import { colors } from '../../config.json'; import { TunnelState } from '../../shared/daemon-rpc-types'; import { messages } from '../../shared/gettext'; import ConnectionPanelContainer from '../containers/ConnectionPanelContainer'; import * as AppButton from './AppButton'; import ImageView from './ImageView'; +import Marquee from './Marquee'; import { IMainButtonProps, ISideButtonProps, MultiButton } from './MultiButton'; import SecuredLabel, { SecuredDisplayStyle } from './SecuredLabel'; @@ -46,16 +47,16 @@ const styles = { fontSize: 16, fontWeight: '800', lineHeight: 22, - marginBottom: 4, + marginBottom: 2, }), status_location: Styles.createTextStyle({ flexDirection: 'column', - marginBottom: 4, + marginBottom: 2, }), status_location_text: Styles.createTextStyle({ fontFamily: 'DINPro', fontSize: 34, - lineHeight: 36, + lineHeight: 38, fontWeight: '900', overflow: 'hidden', letterSpacing: -0.9, @@ -68,8 +69,10 @@ export default class TunnelControl extends Component<ITunnelControlProps> { const Location = ({ children }: { children?: React.ReactNode }) => ( <View style={styles.status_location}>{children}</View> ); - const City = () => <Text style={styles.status_location_text}>{this.props.city}</Text>; - const Country = () => <Text style={styles.status_location_text}>{this.props.country}</Text>; + const City = () => <Marquee style={styles.status_location_text}>{this.props.city}</Marquee>; + const Country = () => ( + <Marquee style={styles.status_location_text}>{this.props.country}</Marquee> + ); const SwitchLocation = () => { return ( |
