/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//! This example creates a 200x200 white rect and allows the user to move it
//! around by using the arrow keys and rotate with '<'/'>'.
//! It does this by using the animation API.

//! The example also features seamless opaque/transparent split of a
//! rounded cornered rectangle, which is done automatically during the
//! scene building for render optimization.

extern crate euclid;
extern crate gleam;
extern crate glutin;
extern crate webrender;
extern crate winit;

#[path = "common/boilerplate.rs"]
mod boilerplate;

use crate::boilerplate::{Example, HandyDandyRectBuilder};
use euclid::Angle;
use webrender::api::*;
use webrender::render_api::*;
use webrender::api::units::*;


struct App {
    property_key0: PropertyBindingKey<LayoutTransform>,
    property_key1: PropertyBindingKey<LayoutTransform>,
    property_key2: PropertyBindingKey<LayoutTransform>,
    opacity_key: PropertyBindingKey<f32>,
    opacity: f32,
    angle0: f32,
    angle1: f32,
    angle2: f32,
}

impl App {
    fn add_rounded_rect(
        &mut self,
        bounds: LayoutRect,
        color: ColorF,
        builder: &mut DisplayListBuilder,
        pipeline_id: PipelineId,
        property_key: PropertyBindingKey<LayoutTransform>,
        opacity_key: Option<PropertyBindingKey<f32>>,
        spatial_tree_item_key: SpatialTreeItemKey,
    ) {
        let filters = match opacity_key {
            Some(opacity_key) => {
                vec![
                    FilterOp::Opacity(PropertyBinding::Binding(opacity_key, self.opacity), self.opacity),
                ]
            }
            None => {
                vec![]
            }
        };

        let spatial_id = builder.push_reference_frame(
            bounds.min,
            SpatialId::root_scroll_node(pipeline_id),
            TransformStyle::Flat,
            PropertyBinding::Binding(property_key, LayoutTransform::identity()),
            ReferenceFrameKind::Transform {
                is_2d_scale_translation: false,
                should_snap: false,
                paired_with_perspective: false,
            },
            spatial_tree_item_key,
        );

        builder.push_simple_stacking_context_with_filters(
            LayoutPoint::zero(),
            spatial_id,
            PrimitiveFlags::IS_BACKFACE_VISIBLE,
            &filters,
            &[],
            &[]
        );

        let space_and_clip = SpaceAndClipInfo {
            spatial_id,
            clip_chain_id: ClipChainId::INVALID,
        };
        let clip_bounds = LayoutRect::from_size(bounds.size());
        let complex_clip = ComplexClipRegion {
            rect: clip_bounds,
            radii: BorderRadius::uniform(30.0),
            mode: ClipMode::Clip,
        };
        let clip_id = builder.define_clip_rounded_rect(
            space_and_clip.spatial_id,
            complex_clip,
        );
        let clip_chain_id = builder.define_clip_chain(None, [clip_id]);

        // Fill it with a white rect
        builder.push_rect(
            &CommonItemProperties::new(
                LayoutRect::from_size(bounds.size()),
                SpaceAndClipInfo {
                    spatial_id,
                    clip_chain_id,
                }
            ),
            LayoutRect::from_size(bounds.size()),
            color,
        );

        builder.pop_stacking_context();
        builder.pop_reference_frame();
    }
}

impl Example for App {
    const WIDTH: u32 = 2048;
    const HEIGHT: u32 = 1536;

    fn render(
        &mut self,
        _api: &mut RenderApi,
        builder: &mut DisplayListBuilder,
        _txn: &mut Transaction,
        _device_size: DeviceIntSize,
        pipeline_id: PipelineId,
        _document_id: DocumentId,
    ) {
        let opacity_key = self.opacity_key;

        let bounds = (150, 150).to(250, 250);
        let key0 = self.property_key0;
        self.add_rounded_rect(
            bounds,
            ColorF::new(1.0, 0.0, 0.0, 0.5),
            builder,
            pipeline_id,
            key0,
            Some(opacity_key),
            SpatialTreeItemKey::new(0, 0)
        );

        let bounds = (400, 400).to(600, 600);
        let key1 = self.property_key1;
        self.add_rounded_rect(
            bounds,
            ColorF::new(0.0, 1.0, 0.0, 0.5),
            builder,
            pipeline_id,
            key1,
            None,
            SpatialTreeItemKey::new(0, 1)
        );

        let bounds = (200, 500).to(350, 580);
        let key2 = self.property_key2;
        self.add_rounded_rect(
            bounds,
            ColorF::new(0.0, 0.0, 1.0, 0.5),
            builder,
            pipeline_id,
            key2,
            None,
            SpatialTreeItemKey::new(0, 2)
        );
    }

    fn on_event(
        &mut self,
        win_event: winit::event::WindowEvent,
        _window: &winit::window::Window,
        api: &mut RenderApi,
        document_id: DocumentId
    ) -> bool {
        let mut rebuild_display_list = false;

        match win_event {
            winit::event::WindowEvent::KeyboardInput {
                input: winit::event::KeyboardInput {
                    state: winit::event::ElementState::Pressed,
                    virtual_keycode: Some(key),
                    ..
                },
                ..
            } => {
                let (delta_angle, delta_opacity) = match key {
                    winit::event::VirtualKeyCode::Down => (0.0, -0.1),
                    winit::event::VirtualKeyCode::Up => (0.0, 0.1),
                    winit::event::VirtualKeyCode::Right => (1.0, 0.0),
                    winit::event::VirtualKeyCode::Left => (-1.0, 0.0),
                    winit::event::VirtualKeyCode::R => {
                        rebuild_display_list = true;
                        (0.0, 0.0)
                    }
                    _ => return false,
                };
                // Update the transform based on the keyboard input and push it to
                // webrender using the generate_frame API. This will recomposite with
                // the updated transform.
                self.opacity += delta_opacity;
                self.angle0 += delta_angle * 0.1;
                self.angle1 += delta_angle * 0.2;
                self.angle2 -= delta_angle * 0.15;
                let xf0 = LayoutTransform::rotation(0.0, 0.0, 1.0, Angle::radians(self.angle0));
                let xf1 = LayoutTransform::rotation(0.0, 0.0, 1.0, Angle::radians(self.angle1));
                let xf2 = LayoutTransform::rotation(0.0, 0.0, 1.0, Angle::radians(self.angle2));
                let mut txn = Transaction::new();
                txn.reset_dynamic_properties();
                txn.append_dynamic_properties(
                    DynamicProperties {
                        transforms: vec![
                            PropertyValue {
                                key: self.property_key0,
                                value: xf0,
                            },
                            PropertyValue {
                                key: self.property_key1,
                                value: xf1,
                            },
                            PropertyValue {
                                key: self.property_key2,
                                value: xf2,
                            },
                        ],
                        floats: vec![
                            PropertyValue {
                                key: self.opacity_key,
                                value: self.opacity,
                            }
                        ],
                        colors: vec![],
                    },
                );
                txn.generate_frame(0, RenderReasons::empty());
                api.send_transaction(document_id, txn);
            }
            _ => (),
        }

        rebuild_display_list
    }
}

fn main() {
    let mut app = App {
        property_key0: PropertyBindingKey::new(42), // arbitrary magic number
        property_key1: PropertyBindingKey::new(44), // arbitrary magic number
        property_key2: PropertyBindingKey::new(45), // arbitrary magic number
        opacity_key: PropertyBindingKey::new(43),
        opacity: 0.5,
        angle0: 0.0,
        angle1: 0.0,
        angle2: 0.0,
    };
    boilerplate::main_wrapper(&mut app, None);
}
