<commit_before>
#![feature(set_stdio)]

extern crate getopts;
extern crate syntax;

pub mod config;
pub mod driver;
pub mod pretty;
pub mod session;
pub mod backend;

use syntax::diagnostics;

use driver::CompileController;
use config::{Input, ErrorOutputType};
use session::{CompileResult, Session};
use pretty::{PpMode, UserIdentifiedItem};

use std::io;
use std::path::PathBuf;

pub fn run(args: Vec<String>) -> isize {
  let (result, session) = run_compiler(&args, &mut RustcDefaultCalls);

  0
}

// Parse args and run the compiler. This is the primary entry point for rustc.
// See comments on CompilerCalls below for details about the callbacks argument.
pub fn run_compiler<'a>(args: &[String],
                        callbacks: &mut CompilerCalls<'a>)
                        -> (CompileResult, Option<Session>) {

  macro_rules! do_or_return {($expr: expr, $sess: expr) => {
    match $expr {
        Compilation::Stop => return (Ok(()), $sess),
        Compilation::Continue => {}
    }
  }}

  let matches = match handle_options(args.to_vec()) {
        Some(matches) => matches,
        None => return (Ok(()), None),
  };

  (Ok(()), None)
}

fn exit_on_err() -> ! {
    // Panic so the process returns a failure code, but don't pollute the
    // output with some unnecessary panic messages, we've already
    // printed everything that we needed to.
    io::set_panic(Box::new(io::sink()));
    panic!();
}

// Whether to stop or continue compilation.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum Compilation {
    Stop,
    Continue,
}

impl Compilation {
    pub fn and_then<F: FnOnce() -> Compilation>(self, next: F) -> Compilation {
        match self {
            Compilation::Stop => Compilation::Stop,
            Compilation::Continue => next(),
        }
    }
}

// A trait for customising the compilation process. Offers a number of hooks for
// executing custom code or customising input.
pub trait CompilerCalls<'a> {
    // Hook for a callback early in the process of handling arguments. This will
    // be called straight after options have been parsed but before anything
    // else (e.g., selecting input and output).
    fn early_callback(&mut self,
                      _: &getopts::Matches,
                      _: &config::Options,
                      _: &diagnostics::registry::Registry,
                      _: ErrorOutputType)
                      -> Compilation {
        Compilation::Continue
    }

    // Hook for a callback late in the process of handling arguments. This will
    // be called just before actual compilation starts (and before build_controller
    // is called), after all arguments etc. have been completely handled.
    fn late_callback(&mut self,
                     _: &getopts::Matches,
                     _: &Session,
                     _: &Input,
                     _: &Option<PathBuf>,
                     _: &Option<PathBuf>)
                     -> Compilation {
        Compilation::Continue
    }

    // Called after we extract the input from the arguments. Gives the implementer
    // an opportunity to change the inputs or to add some custom input handling.
    // The default behaviour is to simply pass through the inputs.
    fn some_input(&mut self,
                  input: Input,
                  input_path: Option<PathBuf>)
                  -> (Input, Option<PathBuf>) {
        (input, input_path)
    }

    // Called after we extract the input from the arguments if there is no valid
    // input. Gives the implementer an opportunity to supply alternate input (by
    // returning a Some value) or to add custom behaviour for this error such as
    // emitting error messages. Returning None will cause compilation to stop
    // at this point.
    fn no_input(&mut self,
                _: &getopts::Matches,
                _: &config::Options,
                _: &Option<PathBuf>,
                _: &Option<PathBuf>,
                _: &diagnostics::registry::Registry)
                -> Option<(Input, Option<PathBuf>)> {
        None
    }

    // Parse pretty printing information from the arguments. The implementer can
    // choose to ignore this (the default will return None) which will skip pretty
    // printing. If you do want to pretty print, it is recommended to use the
    // implementation of this method from RustcDefaultCalls.
    // FIXME, this is a terrible bit of API. Parsing of pretty printing stuff
    // should be done as part of the framework and the implementor should customise
    // handling of it. However, that is not possible atm because pretty printing
    // essentially goes off and takes another path through the compiler which
    // means the session is either moved or not depending on what parse_pretty
    // returns (we could fix this by cloning, but it's another hack). The proper
    // solution is to handle pretty printing as if it were a compiler extension,
    // extending CompileController to make this work (see for example the treatment
    // of save-analysis in RustcDefaultCalls::build_controller).
    fn parse_pretty(&mut self,
                    _sess: &Session,
                    _matches: &getopts::Matches)
                    -> Option<(PpMode, Option<UserIdentifiedItem>)> {
        None
    }

    // Create a CompilController struct for controlling the behaviour of
    // compilation.
    fn build_controller(&mut self, &Session) -> CompileController<'a>;
}

/// Process command line options. Emits messages as appropriate. If compilation
/// should continue, returns a getopts::Matches object parsed from args,
/// otherwise returns None.
///
/// The compiler's handling of options is a little complication as it ties into
/// our stability story, and it's even *more* complicated by historical
/// accidents. The current intention of each compiler option is to have one of
/// three modes:
///
/// 1. An option is stable and can be used everywhere.
/// 2. An option is unstable, but was historically allowed on the stable
///    channel.
/// 3. An option is unstable, and can only be used on nightly.
///
/// Like unstable library and language features, however, unstable options have
/// always required a form of "opt in" to indicate that you're using them. This
/// provides the easy ability to scan a code base to check to see if anything
/// unstable is being used. Currently, this "opt in" is the `-Z` "zed" flag.
///
/// All options behind `-Z` are considered unstable by default. Other top-level
/// options can also be considered unstable, and they were unlocked through the
/// `-Z unstable-options` flag. Note that `-Z` remains to be the root of
/// instability in both cases, though.
///
/// So with all that in mind, the comments below have some more detail about the
/// contortions done here to get things to work out correctly.
pub fn handle_options(mut args: Vec<String>) -> Option<getopts::Matches> {
  None
}

#[derive(Copy, Clone)]
pub struct RustcDefaultCalls;

impl RustcDefaultCalls {
  pub fn test() {}
}

impl<'a> CompilerCalls<'a> for RustcDefaultCalls {
  // Create a CompilController struct for controlling the behaviour of
  // compilation.
  fn build_controller(&mut self, sess: &Session) -> CompileController<'a> {
    CompileController::basic()
  }
}<commit_msg>Implement main function for compiler driver.<commit_after>
#![feature(set_stdio)]

extern crate getopts;
extern crate syntax;

pub mod config;
pub mod driver;
pub mod pretty;
pub mod session;
pub mod backend;

use syntax::diagnostics;

use driver::CompileController;
use config::{Input, ErrorOutputType};
use session::{CompileResult, Session};
use pretty::{PpMode, UserIdentifiedItem};

use std::env;
use std::io;
use std::path::PathBuf;
use std::process;

pub fn run(args: Vec<String>) -> isize {
  let (result, session) = run_compiler(&args, &mut RustcDefaultCalls);

  0
}

// Parse args and run the compiler. This is the primary entry point for rustc.
// See comments on CompilerCalls below for details about the callbacks argument.
pub fn run_compiler<'a>(args: &[String],
                        callbacks: &mut CompilerCalls<'a>)
                        -> (CompileResult, Option<Session>) {

  macro_rules! do_or_return {($expr: expr, $sess: expr) => {
    match $expr {
        Compilation::Stop => return (Ok(()), $sess),
        Compilation::Continue => {}
    }
  }}

  let matches = match handle_options(args.to_vec()) {
        Some(matches) => matches,
        None => return (Ok(()), None),
  };

  (Ok(()), None)
}

fn exit_on_err() -> ! {
    // Panic so the process returns a failure code, but don't pollute the
    // output with some unnecessary panic messages, we've already
    // printed everything that we needed to.
    io::set_panic(Box::new(io::sink()));
    panic!();
}

// Whether to stop or continue compilation.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum Compilation {
    Stop,
    Continue,
}

impl Compilation {
    pub fn and_then<F: FnOnce() -> Compilation>(self, next: F) -> Compilation {
        match self {
            Compilation::Stop => Compilation::Stop,
            Compilation::Continue => next(),
        }
    }
}

// A trait for customising the compilation process. Offers a number of hooks for
// executing custom code or customising input.
pub trait CompilerCalls<'a> {
    // Hook for a callback early in the process of handling arguments. This will
    // be called straight after options have been parsed but before anything
    // else (e.g., selecting input and output).
    fn early_callback(&mut self,
                      _: &getopts::Matches,
                      _: &config::Options,
                      _: &diagnostics::registry::Registry,
                      _: ErrorOutputType)
                      -> Compilation {
        Compilation::Continue
    }

    // Hook for a callback late in the process of handling arguments. This will
    // be called just before actual compilation starts (and before build_controller
    // is called), after all arguments etc. have been completely handled.
    fn late_callback(&mut self,
                     _: &getopts::Matches,
                     _: &Session,
                     _: &Input,
                     _: &Option<PathBuf>,
                     _: &Option<PathBuf>)
                     -> Compilation {
        Compilation::Continue
    }

    // Called after we extract the input from the arguments. Gives the implementer
    // an opportunity to change the inputs or to add some custom input handling.
    // The default behaviour is to simply pass through the inputs.
    fn some_input(&mut self,
                  input: Input,
                  input_path: Option<PathBuf>)
                  -> (Input, Option<PathBuf>) {
        (input, input_path)
    }

    // Called after we extract the input from the arguments if there is no valid
    // input. Gives the implementer an opportunity to supply alternate input (by
    // returning a Some value) or to add custom behaviour for this error such as
    // emitting error messages. Returning None will cause compilation to stop
    // at this point.
    fn no_input(&mut self,
                _: &getopts::Matches,
                _: &config::Options,
                _: &Option<PathBuf>,
                _: &Option<PathBuf>,
                _: &diagnostics::registry::Registry)
                -> Option<(Input, Option<PathBuf>)> {
        None
    }

    // Parse pretty printing information from the arguments. The implementer can
    // choose to ignore this (the default will return None) which will skip pretty
    // printing. If you do want to pretty print, it is recommended to use the
    // implementation of this method from RustcDefaultCalls.
    // FIXME, this is a terrible bit of API. Parsing of pretty printing stuff
    // should be done as part of the framework and the implementor should customise
    // handling of it. However, that is not possible atm because pretty printing
    // essentially goes off and takes another path through the compiler which
    // means the session is either moved or not depending on what parse_pretty
    // returns (we could fix this by cloning, but it's another hack). The proper
    // solution is to handle pretty printing as if it were a compiler extension,
    // extending CompileController to make this work (see for example the treatment
    // of save-analysis in RustcDefaultCalls::build_controller).
    fn parse_pretty(&mut self,
                    _sess: &Session,
                    _matches: &getopts::Matches)
                    -> Option<(PpMode, Option<UserIdentifiedItem>)> {
        None
    }

    // Create a CompilController struct for controlling the behaviour of
    // compilation.
    fn build_controller(&mut self, &Session) -> CompileController<'a>;
}

/// Process command line options. Emits messages as appropriate. If compilation
/// should continue, returns a getopts::Matches object parsed from args,
/// otherwise returns None.
///
/// The compiler's handling of options is a little complication as it ties into
/// our stability story, and it's even *more* complicated by historical
/// accidents. The current intention of each compiler option is to have one of
/// three modes:
///
/// 1. An option is stable and can be used everywhere.
/// 2. An option is unstable, but was historically allowed on the stable
///    channel.
/// 3. An option is unstable, and can only be used on nightly.
///
/// Like unstable library and language features, however, unstable options have
/// always required a form of "opt in" to indicate that you're using them. This
/// provides the easy ability to scan a code base to check to see if anything
/// unstable is being used. Currently, this "opt in" is the `-Z` "zed" flag.
///
/// All options behind `-Z` are considered unstable by default. Other top-level
/// options can also be considered unstable, and they were unlocked through the
/// `-Z unstable-options` flag. Note that `-Z` remains to be the root of
/// instability in both cases, though.
///
/// So with all that in mind, the comments below have some more detail about the
/// contortions done here to get things to work out correctly.
pub fn handle_options(mut args: Vec<String>) -> Option<getopts::Matches> {
  None
}

#[derive(Copy, Clone)]
pub struct RustcDefaultCalls;

impl RustcDefaultCalls {
  pub fn test() {}
}

impl<'a> CompilerCalls<'a> for RustcDefaultCalls {
  // Create a CompilController struct for controlling the behaviour of
  // compilation.
  fn build_controller(&mut self, sess: &Session) -> CompileController<'a> {
    CompileController::basic()
  }
}

pub fn main() {
    let result = run(env::args().collect());
    process::exit(result as i32);
}<|endoftext|>


<commit_before>/* 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/. */

use compositor::IOCompositor;
use windowing::{MouseWindowEvent, WindowMethods};

use azure::azure_hl;
use euclid::length::Length;
use euclid::point::{Point2D, TypedPoint2D};
use euclid::size::TypedSize2D;
use euclid::rect::Rect;
use layers::color::Color;
use layers::geometry::LayerPixel;
use layers::layers::{Layer, LayerBufferSet};
use script_traits::CompositorEvent::{ClickEvent, MouseDownEvent, MouseMoveEvent, MouseUpEvent};
use script_traits::{ScriptControlChan, ConstellationControlMsg};
use msg::compositor_msg::{Epoch, LayerId, LayerProperties, ScrollPolicy};
use msg::constellation_msg::PipelineId;
use std::rc::Rc;

pub struct CompositorData {
    /// This layer's pipeline id. The compositor can associate this id with an
    /// actual CompositionPipeline.
    pub pipeline_id: PipelineId,

    /// The ID of this layer within the pipeline.
    pub id: LayerId,

    /// The behavior of this layer when a scroll message is received.
    pub wants_scroll_events: WantsScrollEventsFlag,

    /// Whether an ancestor layer that receives scroll events moves this layer.
    pub scroll_policy: ScrollPolicy,

    /// The epoch that has been requested for this layer (via send_buffer_requests).
    pub requested_epoch: Epoch,

    /// The last accepted painted buffer for this layer (via assign_pained_buffers).
    pub painted_epoch: Epoch,

    /// The scroll offset originating from this scrolling root. This allows scrolling roots
    /// to track their current scroll position even while their content_offset does not change.
    pub scroll_offset: TypedPoint2D<LayerPixel, f32>,
}

impl CompositorData {
    pub fn new_layer(pipeline_id: PipelineId,
                     layer_properties: LayerProperties,
                     wants_scroll_events: WantsScrollEventsFlag,
                     tile_size: usize)
                     -> Rc<Layer<CompositorData>> {
        let new_compositor_data = CompositorData {
            pipeline_id: pipeline_id,
            id: layer_properties.id,
            wants_scroll_events: wants_scroll_events,
            scroll_policy: layer_properties.scroll_policy,
            requested_epoch: Epoch(0),
            painted_epoch: Epoch(0),
            scroll_offset: Point2D::typed(0., 0.),
        };

        Rc::new(Layer::new(Rect::from_untyped(&layer_properties.rect),
                           tile_size,
                           to_layers_color(&layer_properties.background_color),
                           1.0,
                           layer_properties.establishes_3d_context,
                           new_compositor_data))
    }
}

pub trait CompositorLayer {
    fn update_layer_except_bounds(&self, layer_properties: LayerProperties);

    fn update_layer(&self, layer_properties: LayerProperties);

    fn add_buffers<Window>(&self,
                           compositor: &mut IOCompositor<Window>,
                           new_buffers: Box<LayerBufferSet>,
                           epoch: Epoch)
                           where Window: WindowMethods;

    /// Destroys all layer tiles, sending the buffers back to the painter to be destroyed or
    /// reused.
    fn clear<Window>(&self, compositor: &mut IOCompositor<Window>) where Window: WindowMethods;

    /// Destroys tiles for this layer and all descendent layers, sending the buffers back to the
    /// painter to be destroyed or reused.
    fn clear_all_tiles<Window>(&self, compositor: &mut IOCompositor<Window>)
                               where Window: WindowMethods;

    /// Removes the root layer (and any children) for a given pipeline from the
    /// compositor. Buffers that the compositor is holding are returned to the
    /// owning paint task.
    fn remove_root_layer_with_pipeline_id<Window>(&self,
                                                  compositor: &mut IOCompositor<Window>,
                                                  pipeline_id: PipelineId)
                                                  where Window: WindowMethods;

    /// Destroys all tiles of all layers, including children, *without* sending them back to the
    /// painter. You must call this only when the paint task is destined to be going down;
    /// otherwise, you will leak tiles.
    ///
    /// This is used during shutdown, when we know the paint task is going away.
    fn forget_all_tiles(&self);

    /// Move the layer's descendants that don't want scroll events and scroll by a relative
    /// specified amount in page coordinates. This also takes in a cursor position to see if the
    /// mouse is over child layers first. If a layer successfully scrolled returns either
    /// ScrollPositionUnchanged or ScrollPositionChanged. If no layer was targeted by the event
    /// returns ScrollEventUnhandled.
    fn handle_scroll_event(&self,
                           delta: TypedPoint2D<LayerPixel, f32>,
                           cursor: TypedPoint2D<LayerPixel, f32>)
                           -> ScrollEventResult;

    // Takes in a MouseWindowEvent, determines if it should be passed to children, and
    // sends the event off to the appropriate pipeline. NB: the cursor position is in
    // page coordinates.
    fn send_mouse_event<Window>(&self,
                                compositor: &IOCompositor<Window>,
                                event: MouseWindowEvent,
                                cursor: TypedPoint2D<LayerPixel, f32>)
                                where Window: WindowMethods;

    fn send_mouse_move_event<Window>(&self,
                                     compositor: &IOCompositor<Window>,
                                     cursor: TypedPoint2D<LayerPixel, f32>)
                                     where Window: WindowMethods;

    fn clamp_scroll_offset_and_scroll_layer(&self,
                                            new_offset: TypedPoint2D<LayerPixel, f32>)
                                            -> ScrollEventResult;

    fn scroll_layer_and_all_child_layers(&self,
                                         new_offset: TypedPoint2D<LayerPixel, f32>)
                                         -> bool;

    /// Return a flag describing how this layer deals with scroll events.
    fn wants_scroll_events(&self) -> WantsScrollEventsFlag;

    /// Return the pipeline id associated with this layer.
    fn pipeline_id(&self) -> PipelineId;
}

#[derive(Copy, PartialEq, Clone)]
pub enum WantsScrollEventsFlag {
    WantsScrollEvents,
    DoesntWantScrollEvents,
}

fn to_layers_color(color: &azure_hl::Color) -> Color {
    Color { r: color.r, g: color.g, b: color.b, a: color.a }
}

trait Clampable {
    fn clamp(&self, mn: &Self, mx: &Self) -> Self;
}

impl Clampable for f32 {
    /// Returns the number constrained within the range `mn <= self <= mx`.
    /// If any of the numbers are `NAN` then `NAN` is returned.
    #[inline]
    fn clamp(&self, mn: &f32, mx: &f32) -> f32 {
        match () {
            _ if self.is_nan()   => *self,
            _ if !(*self <= *mx) => *mx,
            _ if !(*self >= *mn) => *mn,
            _                    => *self,
        }
    }
}

fn calculate_content_size_for_layer(layer: &Layer<CompositorData>)
                                    -> TypedSize2D<LayerPixel, f32> {
    layer.children().iter().fold(Rect::zero(),
                                 |unioned_rect, child_rect| {
                                    unioned_rect.union(&*child_rect.bounds.borrow())
                                 }).size
}

#[derive(PartialEq)]
pub enum ScrollEventResult {
    ScrollEventUnhandled,
    ScrollPositionChanged,
    ScrollPositionUnchanged,
}

impl CompositorLayer for Layer<CompositorData> {
    fn update_layer_except_bounds(&self, layer_properties: LayerProperties) {
        self.extra_data.borrow_mut().scroll_policy = layer_properties.scroll_policy;
        *self.transform.borrow_mut() = layer_properties.transform;
        *self.perspective.borrow_mut() = layer_properties.perspective;

        *self.background_color.borrow_mut() = to_layers_color(&layer_properties.background_color);

        self.contents_changed();
    }

    fn update_layer(&self, layer_properties: LayerProperties) {
        *self.bounds.borrow_mut() = Rect::from_untyped(&layer_properties.rect);

        // Call scroll for bounds checking if the page shrunk. Use (-1, -1) as the
        // cursor position to make sure the scroll isn't propagated downwards.
        self.handle_scroll_event(Point2D::typed(0f32, 0f32), Point2D::typed(-1f32, -1f32));
        self.update_layer_except_bounds(layer_properties);
    }

    // Add LayerBuffers to the specified layer. Returns the layer buffer set back if the layer that
    // matches the given pipeline ID was not found; otherwise returns None and consumes the layer
    // buffer set.
    //
    // If the epoch of the message does not match the layer's epoch, the message is ignored, the
    // layer buffer set is consumed, and None is returned.
    fn add_buffers<Window>(&self,
                           compositor: &mut IOCompositor<Window>,
                           new_buffers: Box<LayerBufferSet>,
                           epoch: Epoch)
                           where Window: WindowMethods {
        self.extra_data.borrow_mut().painted_epoch = epoch;
        assert!(self.extra_data.borrow().painted_epoch == self.extra_data.borrow().requested_epoch);

        for buffer in new_buffers.buffers.into_iter().rev() {
            self.add_buffer(buffer);
        }

        compositor.cache_unused_buffers(self.collect_unused_buffers())
    }

    fn clear<Window>(&self, compositor: &mut IOCompositor<Window>) where Window: WindowMethods {
        let buffers = self.collect_buffers();

        if !buffers.is_empty() {
            compositor.cache_unused_buffers(buffers);
        }
    }

    /// Destroys tiles for this layer and all descendent layers, sending the buffers back to the
    /// painter to be destroyed or reused.
    fn clear_all_tiles<Window>(&self,
                               compositor: &mut IOCompositor<Window>)
                               where Window: WindowMethods {
        self.clear(compositor);
        for kid in self.children().iter() {
            kid.clear_all_tiles(compositor);
        }
    }

    fn remove_root_layer_with_pipeline_id<Window>(&self,
                                                  compositor: &mut IOCompositor<Window>,
                                                  pipeline_id: PipelineId)
                                                  where Window: WindowMethods {
        // Find the child that is the root layer for this pipeline.
        let index = self.children().iter().position(|kid| {
            let extra_data = kid.extra_data.borrow();
            extra_data.pipeline_id == pipeline_id && extra_data.id == LayerId::null()
        });

        match index {
            Some(index) => {
                // Remove the root layer, and return buffers to the paint task
                let child = self.children().remove(index);
                child.clear_all_tiles(compositor);
            }
            None => {
                // Wasn't found, recurse into child layers
                for kid in self.children().iter() {
                    kid.remove_root_layer_with_pipeline_id(compositor, pipeline_id);
                }
            }
        }
    }

    /// Destroys all tiles of all layers, including children, *without* sending them back to the
    /// painter. You must call this only when the paint task is destined to be going down;
    /// otherwise, you will leak tiles.
    ///
    /// This is used during shutdown, when we know the paint task is going away.
    fn forget_all_tiles(&self) {
        let tiles = self.collect_buffers();
        for tile in tiles.into_iter() {
            let mut tile = tile;
            tile.mark_wont_leak()
        }

        for kid in self.children().iter() {
            kid.forget_all_tiles();
        }
    }

    fn handle_scroll_event(&self,
                           delta: TypedPoint2D<LayerPixel, f32>,
                           cursor: TypedPoint2D<LayerPixel, f32>)
                           -> ScrollEventResult {
        // If this layer doesn't want scroll events, neither it nor its children can handle scroll
        // events.
        if self.wants_scroll_events() != WantsScrollEventsFlag::WantsScrollEvents {
            return ScrollEventResult::ScrollEventUnhandled;
        }

        //// Allow children to scroll.
        let scroll_offset = self.extra_data.borrow().scroll_offset;
        let new_cursor = cursor - scroll_offset;
        for child in self.children().iter() {
            let child_bounds = child.bounds.borrow();
            if child_bounds.contains(&new_cursor) {
                let result = child.handle_scroll_event(delta, new_cursor - child_bounds.origin);
                if result != ScrollEventResult::ScrollEventUnhandled {
                    return result;
                }
            }
        }

        self.clamp_scroll_offset_and_scroll_layer(scroll_offset + delta)
    }

    fn clamp_scroll_offset_and_scroll_layer(&self, new_offset: TypedPoint2D<LayerPixel, f32>)
                                            -> ScrollEventResult {
        let layer_size = self.bounds.borrow().size;
        let content_size = calculate_content_size_for_layer(self);
        let min_x = (layer_size.width - content_size.width).get().min(0.0);
        let min_y = (layer_size.height - content_size.height).get().min(0.0);
        let new_offset : TypedPoint2D<LayerPixel, f32> =
            Point2D::new(Length::new(new_offset.x.get().clamp(&min_x, &0.0)),
                         Length::new(new_offset.y.get().clamp(&min_y, &0.0)));

        if self.extra_data.borrow().scroll_offset == new_offset {
            return ScrollEventResult::ScrollPositionUnchanged;
        }

        // The scroll offset is just a record of the scroll position of this scrolling root,
        // but scroll_layer_and_all_child_layers actually moves the child layers.
        self.extra_data.borrow_mut().scroll_offset = new_offset;

        let mut result = false;
        for child in self.children().iter() {
            result |= child.scroll_layer_and_all_child_layers(new_offset);
        }

        if result {
            return ScrollEventResult::ScrollPositionChanged;
        } else {
            return ScrollEventResult::ScrollPositionUnchanged;
        }
    }

    fn send_mouse_event<Window>(&self,
                                compositor: &IOCompositor<Window>,
                                event: MouseWindowEvent,
                                cursor: TypedPoint2D<LayerPixel, f32>)
                                where Window: WindowMethods {
        let event_point = cursor.to_untyped();
        let message = match event {
            MouseWindowEvent::Click(button, _) =>
                ClickEvent(button, event_point),
            MouseWindowEvent::MouseDown(button, _) =>
                MouseDownEvent(button, event_point),
            MouseWindowEvent::MouseUp(button, _) =>
                MouseUpEvent(button, event_point),
        };

        let pipeline = compositor.get_pipeline(self.pipeline_id());
        let ScriptControlChan(ref chan) = pipeline.script_chan;
        let _ = chan.send(ConstellationControlMsg::SendEvent(pipeline.id.clone(), message));
    }

    fn send_mouse_move_event<Window>(&self,
                                     compositor: &IOCompositor<Window>,
                                     cursor: TypedPoint2D<LayerPixel, f32>)
                                     where Window: WindowMethods {
        let message = MouseMoveEvent(cursor.to_untyped());
        let pipeline = compositor.get_pipeline(self.pipeline_id());
        let ScriptControlChan(ref chan) = pipeline.script_chan;
        let _ = chan.send(ConstellationControlMsg::SendEvent(pipeline.id.clone(), message));
    }

    fn scroll_layer_and_all_child_layers(&self, new_offset: TypedPoint2D<LayerPixel, f32>)
                                         -> bool {
        let mut result = false;

        // Only scroll this layer if it's not fixed-positioned.
        if self.extra_data.borrow().scroll_policy != ScrollPolicy::FixedPosition {
            let new_offset = new_offset.to_untyped();
            *self.content_offset.borrow_mut() = Point2D::from_untyped(&new_offset);
            result = true
        }

        let offset_for_children = new_offset + self.extra_data.borrow().scroll_offset;
        for child in self.children().iter() {
            result |= child.scroll_layer_and_all_child_layers(offset_for_children);
        }

        return result;
    }

    fn wants_scroll_events(&self) -> WantsScrollEventsFlag {
        self.extra_data.borrow().wants_scroll_events
    }

    fn pipeline_id(&self) -> PipelineId {
        self.extra_data.borrow().pipeline_id
    }
}
<commit_msg>compositor: Allow children of layers that don't want scroll events to be scrolled.<commit_after>/* 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/. */

use compositor::IOCompositor;
use windowing::{MouseWindowEvent, WindowMethods};

use azure::azure_hl;
use euclid::length::Length;
use euclid::point::{Point2D, TypedPoint2D};
use euclid::size::TypedSize2D;
use euclid::rect::Rect;
use layers::color::Color;
use layers::geometry::LayerPixel;
use layers::layers::{Layer, LayerBufferSet};
use script_traits::CompositorEvent::{ClickEvent, MouseDownEvent, MouseMoveEvent, MouseUpEvent};
use script_traits::{ScriptControlChan, ConstellationControlMsg};
use msg::compositor_msg::{Epoch, LayerId, LayerProperties, ScrollPolicy};
use msg::constellation_msg::PipelineId;
use std::rc::Rc;

pub struct CompositorData {
    /// This layer's pipeline id. The compositor can associate this id with an
    /// actual CompositionPipeline.
    pub pipeline_id: PipelineId,

    /// The ID of this layer within the pipeline.
    pub id: LayerId,

    /// The behavior of this layer when a scroll message is received.
    pub wants_scroll_events: WantsScrollEventsFlag,

    /// Whether an ancestor layer that receives scroll events moves this layer.
    pub scroll_policy: ScrollPolicy,

    /// The epoch that has been requested for this layer (via send_buffer_requests).
    pub requested_epoch: Epoch,

    /// The last accepted painted buffer for this layer (via assign_pained_buffers).
    pub painted_epoch: Epoch,

    /// The scroll offset originating from this scrolling root. This allows scrolling roots
    /// to track their current scroll position even while their content_offset does not change.
    pub scroll_offset: TypedPoint2D<LayerPixel, f32>,
}

impl CompositorData {
    pub fn new_layer(pipeline_id: PipelineId,
                     layer_properties: LayerProperties,
                     wants_scroll_events: WantsScrollEventsFlag,
                     tile_size: usize)
                     -> Rc<Layer<CompositorData>> {
        let new_compositor_data = CompositorData {
            pipeline_id: pipeline_id,
            id: layer_properties.id,
            wants_scroll_events: wants_scroll_events,
            scroll_policy: layer_properties.scroll_policy,
            requested_epoch: Epoch(0),
            painted_epoch: Epoch(0),
            scroll_offset: Point2D::typed(0., 0.),
        };

        Rc::new(Layer::new(Rect::from_untyped(&layer_properties.rect),
                           tile_size,
                           to_layers_color(&layer_properties.background_color),
                           1.0,
                           layer_properties.establishes_3d_context,
                           new_compositor_data))
    }
}

pub trait CompositorLayer {
    fn update_layer_except_bounds(&self, layer_properties: LayerProperties);

    fn update_layer(&self, layer_properties: LayerProperties);

    fn add_buffers<Window>(&self,
                           compositor: &mut IOCompositor<Window>,
                           new_buffers: Box<LayerBufferSet>,
                           epoch: Epoch)
                           where Window: WindowMethods;

    /// Destroys all layer tiles, sending the buffers back to the painter to be destroyed or
    /// reused.
    fn clear<Window>(&self, compositor: &mut IOCompositor<Window>) where Window: WindowMethods;

    /// Destroys tiles for this layer and all descendent layers, sending the buffers back to the
    /// painter to be destroyed or reused.
    fn clear_all_tiles<Window>(&self, compositor: &mut IOCompositor<Window>)
                               where Window: WindowMethods;

    /// Removes the root layer (and any children) for a given pipeline from the
    /// compositor. Buffers that the compositor is holding are returned to the
    /// owning paint task.
    fn remove_root_layer_with_pipeline_id<Window>(&self,
                                                  compositor: &mut IOCompositor<Window>,
                                                  pipeline_id: PipelineId)
                                                  where Window: WindowMethods;

    /// Destroys all tiles of all layers, including children, *without* sending them back to the
    /// painter. You must call this only when the paint task is destined to be going down;
    /// otherwise, you will leak tiles.
    ///
    /// This is used during shutdown, when we know the paint task is going away.
    fn forget_all_tiles(&self);

    /// Move the layer's descendants that don't want scroll events and scroll by a relative
    /// specified amount in page coordinates. This also takes in a cursor position to see if the
    /// mouse is over child layers first. If a layer successfully scrolled returns either
    /// ScrollPositionUnchanged or ScrollPositionChanged. If no layer was targeted by the event
    /// returns ScrollEventUnhandled.
    fn handle_scroll_event(&self,
                           delta: TypedPoint2D<LayerPixel, f32>,
                           cursor: TypedPoint2D<LayerPixel, f32>)
                           -> ScrollEventResult;

    // Takes in a MouseWindowEvent, determines if it should be passed to children, and
    // sends the event off to the appropriate pipeline. NB: the cursor position is in
    // page coordinates.
    fn send_mouse_event<Window>(&self,
                                compositor: &IOCompositor<Window>,
                                event: MouseWindowEvent,
                                cursor: TypedPoint2D<LayerPixel, f32>)
                                where Window: WindowMethods;

    fn send_mouse_move_event<Window>(&self,
                                     compositor: &IOCompositor<Window>,
                                     cursor: TypedPoint2D<LayerPixel, f32>)
                                     where Window: WindowMethods;

    fn clamp_scroll_offset_and_scroll_layer(&self,
                                            new_offset: TypedPoint2D<LayerPixel, f32>)
                                            -> ScrollEventResult;

    fn scroll_layer_and_all_child_layers(&self,
                                         new_offset: TypedPoint2D<LayerPixel, f32>)
                                         -> bool;

    /// Return a flag describing how this layer deals with scroll events.
    fn wants_scroll_events(&self) -> WantsScrollEventsFlag;

    /// Return the pipeline id associated with this layer.
    fn pipeline_id(&self) -> PipelineId;
}

#[derive(Copy, PartialEq, Clone)]
pub enum WantsScrollEventsFlag {
    WantsScrollEvents,
    DoesntWantScrollEvents,
}

fn to_layers_color(color: &azure_hl::Color) -> Color {
    Color { r: color.r, g: color.g, b: color.b, a: color.a }
}

trait Clampable {
    fn clamp(&self, mn: &Self, mx: &Self) -> Self;
}

impl Clampable for f32 {
    /// Returns the number constrained within the range `mn <= self <= mx`.
    /// If any of the numbers are `NAN` then `NAN` is returned.
    #[inline]
    fn clamp(&self, mn: &f32, mx: &f32) -> f32 {
        match () {
            _ if self.is_nan()   => *self,
            _ if !(*self <= *mx) => *mx,
            _ if !(*self >= *mn) => *mn,
            _                    => *self,
        }
    }
}

fn calculate_content_size_for_layer(layer: &Layer<CompositorData>)
                                    -> TypedSize2D<LayerPixel, f32> {
    layer.children().iter().fold(Rect::zero(),
                                 |unioned_rect, child_rect| {
                                    unioned_rect.union(&*child_rect.bounds.borrow())
                                 }).size
}

#[derive(PartialEq)]
pub enum ScrollEventResult {
    ScrollEventUnhandled,
    ScrollPositionChanged,
    ScrollPositionUnchanged,
}

impl CompositorLayer for Layer<CompositorData> {
    fn update_layer_except_bounds(&self, layer_properties: LayerProperties) {
        self.extra_data.borrow_mut().scroll_policy = layer_properties.scroll_policy;
        *self.transform.borrow_mut() = layer_properties.transform;
        *self.perspective.borrow_mut() = layer_properties.perspective;

        *self.background_color.borrow_mut() = to_layers_color(&layer_properties.background_color);

        self.contents_changed();
    }

    fn update_layer(&self, layer_properties: LayerProperties) {
        *self.bounds.borrow_mut() = Rect::from_untyped(&layer_properties.rect);

        // Call scroll for bounds checking if the page shrunk. Use (-1, -1) as the
        // cursor position to make sure the scroll isn't propagated downwards.
        self.handle_scroll_event(Point2D::typed(0f32, 0f32), Point2D::typed(-1f32, -1f32));
        self.update_layer_except_bounds(layer_properties);
    }

    // Add LayerBuffers to the specified layer. Returns the layer buffer set back if the layer that
    // matches the given pipeline ID was not found; otherwise returns None and consumes the layer
    // buffer set.
    //
    // If the epoch of the message does not match the layer's epoch, the message is ignored, the
    // layer buffer set is consumed, and None is returned.
    fn add_buffers<Window>(&self,
                           compositor: &mut IOCompositor<Window>,
                           new_buffers: Box<LayerBufferSet>,
                           epoch: Epoch)
                           where Window: WindowMethods {
        self.extra_data.borrow_mut().painted_epoch = epoch;
        assert!(self.extra_data.borrow().painted_epoch == self.extra_data.borrow().requested_epoch);

        for buffer in new_buffers.buffers.into_iter().rev() {
            self.add_buffer(buffer);
        }

        compositor.cache_unused_buffers(self.collect_unused_buffers())
    }

    fn clear<Window>(&self, compositor: &mut IOCompositor<Window>) where Window: WindowMethods {
        let buffers = self.collect_buffers();

        if !buffers.is_empty() {
            compositor.cache_unused_buffers(buffers);
        }
    }

    /// Destroys tiles for this layer and all descendent layers, sending the buffers back to the
    /// painter to be destroyed or reused.
    fn clear_all_tiles<Window>(&self,
                               compositor: &mut IOCompositor<Window>)
                               where Window: WindowMethods {
        self.clear(compositor);
        for kid in self.children().iter() {
            kid.clear_all_tiles(compositor);
        }
    }

    fn remove_root_layer_with_pipeline_id<Window>(&self,
                                                  compositor: &mut IOCompositor<Window>,
                                                  pipeline_id: PipelineId)
                                                  where Window: WindowMethods {
        // Find the child that is the root layer for this pipeline.
        let index = self.children().iter().position(|kid| {
            let extra_data = kid.extra_data.borrow();
            extra_data.pipeline_id == pipeline_id && extra_data.id == LayerId::null()
        });

        match index {
            Some(index) => {
                // Remove the root layer, and return buffers to the paint task
                let child = self.children().remove(index);
                child.clear_all_tiles(compositor);
            }
            None => {
                // Wasn't found, recurse into child layers
                for kid in self.children().iter() {
                    kid.remove_root_layer_with_pipeline_id(compositor, pipeline_id);
                }
            }
        }
    }

    /// Destroys all tiles of all layers, including children, *without* sending them back to the
    /// painter. You must call this only when the paint task is destined to be going down;
    /// otherwise, you will leak tiles.
    ///
    /// This is used during shutdown, when we know the paint task is going away.
    fn forget_all_tiles(&self) {
        let tiles = self.collect_buffers();
        for tile in tiles.into_iter() {
            let mut tile = tile;
            tile.mark_wont_leak()
        }

        for kid in self.children().iter() {
            kid.forget_all_tiles();
        }
    }

    fn handle_scroll_event(&self,
                           delta: TypedPoint2D<LayerPixel, f32>,
                           cursor: TypedPoint2D<LayerPixel, f32>)
                           -> ScrollEventResult {
        // Allow children to scroll.
        let scroll_offset = self.extra_data.borrow().scroll_offset;
        let new_cursor = cursor - scroll_offset;
        for child in self.children().iter() {
            let child_bounds = child.bounds.borrow();
            if child_bounds.contains(&new_cursor) {
                let result = child.handle_scroll_event(delta, new_cursor - child_bounds.origin);
                if result != ScrollEventResult::ScrollEventUnhandled {
                    return result;
                }
            }
        }

        // If this layer doesn't want scroll events, it can't handle scroll events.
        if self.wants_scroll_events() != WantsScrollEventsFlag::WantsScrollEvents {
            return ScrollEventResult::ScrollEventUnhandled;
        }

        self.clamp_scroll_offset_and_scroll_layer(scroll_offset + delta)
    }

    fn clamp_scroll_offset_and_scroll_layer(&self, new_offset: TypedPoint2D<LayerPixel, f32>)
                                            -> ScrollEventResult {
        let layer_size = self.bounds.borrow().size;
        let content_size = calculate_content_size_for_layer(self);
        let min_x = (layer_size.width - content_size.width).get().min(0.0);
        let min_y = (layer_size.height - content_size.height).get().min(0.0);
        let new_offset : TypedPoint2D<LayerPixel, f32> =
            Point2D::new(Length::new(new_offset.x.get().clamp(&min_x, &0.0)),
                         Length::new(new_offset.y.get().clamp(&min_y, &0.0)));

        if self.extra_data.borrow().scroll_offset == new_offset {
            return ScrollEventResult::ScrollPositionUnchanged;
        }

        // The scroll offset is just a record of the scroll position of this scrolling root,
        // but scroll_layer_and_all_child_layers actually moves the child layers.
        self.extra_data.borrow_mut().scroll_offset = new_offset;

        let mut result = false;
        for child in self.children().iter() {
            result |= child.scroll_layer_and_all_child_layers(new_offset);
        }

        if result {
            return ScrollEventResult::ScrollPositionChanged;
        } else {
            return ScrollEventResult::ScrollPositionUnchanged;
        }
    }

    fn send_mouse_event<Window>(&self,
                                compositor: &IOCompositor<Window>,
                                event: MouseWindowEvent,
                                cursor: TypedPoint2D<LayerPixel, f32>)
                                where Window: WindowMethods {
        let event_point = cursor.to_untyped();
        let message = match event {
            MouseWindowEvent::Click(button, _) =>
                ClickEvent(button, event_point),
            MouseWindowEvent::MouseDown(button, _) =>
                MouseDownEvent(button, event_point),
            MouseWindowEvent::MouseUp(button, _) =>
                MouseUpEvent(button, event_point),
        };

        let pipeline = compositor.get_pipeline(self.pipeline_id());
        let ScriptControlChan(ref chan) = pipeline.script_chan;
        let _ = chan.send(ConstellationControlMsg::SendEvent(pipeline.id.clone(), message));
    }

    fn send_mouse_move_event<Window>(&self,
                                     compositor: &IOCompositor<Window>,
                                     cursor: TypedPoint2D<LayerPixel, f32>)
                                     where Window: WindowMethods {
        let message = MouseMoveEvent(cursor.to_untyped());
        let pipeline = compositor.get_pipeline(self.pipeline_id());
        let ScriptControlChan(ref chan) = pipeline.script_chan;
        let _ = chan.send(ConstellationControlMsg::SendEvent(pipeline.id.clone(), message));
    }

    fn scroll_layer_and_all_child_layers(&self, new_offset: TypedPoint2D<LayerPixel, f32>)
                                         -> bool {
        let mut result = false;

        // Only scroll this layer if it's not fixed-positioned.
        if self.extra_data.borrow().scroll_policy != ScrollPolicy::FixedPosition {
            let new_offset = new_offset.to_untyped();
            *self.content_offset.borrow_mut() = Point2D::from_untyped(&new_offset);
            result = true
        }

        let offset_for_children = new_offset + self.extra_data.borrow().scroll_offset;
        for child in self.children().iter() {
            result |= child.scroll_layer_and_all_child_layers(offset_for_children);
        }

        return result;
    }

    fn wants_scroll_events(&self) -> WantsScrollEventsFlag {
        self.extra_data.borrow().wants_scroll_events
    }

    fn pipeline_id(&self) -> PipelineId {
        self.extra_data.borrow().pipeline_id
    }
}
<|endoftext|>


<commit_before>/* 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/. */

// FIXME(rust-lang/rust#26264): Remove GenericEndingShape and GenericGradientItem.

use app_units::Au;
use crate::display_list::ToLayout;
use euclid::{Point2D, Size2D, Vector2D};
use style::properties::ComputedValues;
use style::values::computed::image::{EndingShape, LineDirection};
use style::values::computed::{Angle, GradientItem, LengthOrPercentage, Percentage, Position};
use style::values::generics::image::EndingShape as GenericEndingShape;
use style::values::generics::image::GradientItem as GenericGradientItem;
use style::values::generics::image::{Circle, Ellipse, ShapeExtent};
use style::values::specified::position::{X, Y};
use webrender_api::{ExtendMode, Gradient, GradientBuilder, GradientStop, RadialGradient};

/// A helper data structure for gradients.
#[derive(Clone, Copy)]
struct StopRun {
    start_offset: f32,
    end_offset: f32,
    start_index: usize,
    stop_count: usize,
}

/// Determines the radius of a circle if it was not explictly provided.
/// <https://drafts.csswg.org/css-images-3/#typedef-size>
fn circle_size_keyword(
    keyword: ShapeExtent,
    size: &Size2D<Au>,
    center: &Point2D<Au>,
) -> Size2D<Au> {
    let radius = match keyword {
        ShapeExtent::ClosestSide | ShapeExtent::Contain => {
            let dist = distance_to_sides(size, center, ::std::cmp::min);
            ::std::cmp::min(dist.width, dist.height)
        },
        ShapeExtent::FarthestSide => {
            let dist = distance_to_sides(size, center, ::std::cmp::max);
            ::std::cmp::max(dist.width, dist.height)
        },
        ShapeExtent::ClosestCorner => distance_to_corner(size, center, ::std::cmp::min),
        ShapeExtent::FarthestCorner | ShapeExtent::Cover => {
            distance_to_corner(size, center, ::std::cmp::max)
        },
    };
    Size2D::new(radius, radius)
}

/// Returns the radius for an ellipse with the same ratio as if it was matched to the sides.
fn ellipse_radius<F>(size: &Size2D<Au>, center: &Point2D<Au>, cmp: F) -> Size2D<Au>
where
    F: Fn(Au, Au) -> Au,
{
    let dist = distance_to_sides(size, center, cmp);
    Size2D::new(
        dist.width.scale_by(::std::f32::consts::FRAC_1_SQRT_2 * 2.0),
        dist.height
            .scale_by(::std::f32::consts::FRAC_1_SQRT_2 * 2.0),
    )
}

/// Determines the radius of an ellipse if it was not explictly provided.
/// <https://drafts.csswg.org/css-images-3/#typedef-size>
fn ellipse_size_keyword(
    keyword: ShapeExtent,
    size: &Size2D<Au>,
    center: &Point2D<Au>,
) -> Size2D<Au> {
    match keyword {
        ShapeExtent::ClosestSide | ShapeExtent::Contain => {
            distance_to_sides(size, center, ::std::cmp::min)
        },
        ShapeExtent::FarthestSide => distance_to_sides(size, center, ::std::cmp::max),
        ShapeExtent::ClosestCorner => ellipse_radius(size, center, ::std::cmp::min),
        ShapeExtent::FarthestCorner | ShapeExtent::Cover => {
            ellipse_radius(size, center, ::std::cmp::max)
        },
    }
}

fn convert_gradient_stops(
    style: &ComputedValues,
    gradient_items: &[GradientItem],
    total_length: Au,
) -> GradientBuilder {
    // Determine the position of each stop per CSS-IMAGES § 3.4.

    // Only keep the color stops, discard the color interpolation hints.
    let mut stop_items = gradient_items
        .iter()
        .filter_map(|item| match *item {
            GenericGradientItem::ColorStop(ref stop) => Some(*stop),
            _ => None,
        })
        .collect::<Vec<_>>();

    assert!(stop_items.len() >= 2);

    // Run the algorithm from
    // https://drafts.csswg.org/css-images-3/#color-stop-syntax

    // Step 1:
    // If the first color stop does not have a position, set its position to 0%.
    {
        let first = stop_items.first_mut().unwrap();
        if first.position.is_none() {
            first.position = Some(LengthOrPercentage::Percentage(Percentage(0.0)));
        }
    }
    // If the last color stop does not have a position, set its position to 100%.
    {
        let last = stop_items.last_mut().unwrap();
        if last.position.is_none() {
            last.position = Some(LengthOrPercentage::Percentage(Percentage(1.0)));
        }
    }

    // Step 2: Move any stops placed before earlier stops to the
    // same position as the preceding stop.
    let mut last_stop_position = stop_items.first().unwrap().position.unwrap();
    for stop in stop_items.iter_mut().skip(1) {
        if let Some(pos) = stop.position {
            if position_to_offset(last_stop_position, total_length) >
                position_to_offset(pos, total_length)
            {
                stop.position = Some(last_stop_position);
            }
            last_stop_position = stop.position.unwrap();
        }
    }

    // Step 3: Evenly space stops without position.
    let mut stops = GradientBuilder::new();
    let mut stop_run = None;
    for (i, stop) in stop_items.iter().enumerate() {
        let offset = match stop.position {
            None => {
                if stop_run.is_none() {
                    // Initialize a new stop run.
                    // `unwrap()` here should never fail because this is the beginning of
                    // a stop run, which is always bounded by a length or percentage.
                    let start_offset =
                        position_to_offset(stop_items[i - 1].position.unwrap(), total_length);
                    // `unwrap()` here should never fail because this is the end of
                    // a stop run, which is always bounded by a length or percentage.
                    let (end_index, end_stop) = stop_items[(i + 1)..]
                        .iter()
                        .enumerate()
                        .find(|&(_, ref stop)| stop.position.is_some())
                        .unwrap();
                    let end_offset = position_to_offset(end_stop.position.unwrap(), total_length);
                    stop_run = Some(StopRun {
                        start_offset,
                        end_offset,
                        start_index: i - 1,
                        stop_count: end_index,
                    })
                }

                let stop_run = stop_run.unwrap();
                let stop_run_length = stop_run.end_offset - stop_run.start_offset;
                stop_run.start_offset +
                    stop_run_length * (i - stop_run.start_index) as f32 /
                        ((2 + stop_run.stop_count) as f32)
            },
            Some(position) => {
                stop_run = None;
                position_to_offset(position, total_length)
            },
        };
        assert!(offset.is_finite());
        stops.push(GradientStop {
            offset: offset,
            color: style.resolve_color(stop.color).to_layout(),
        })
    }
    stops
}

fn extend_mode(repeating: bool) -> ExtendMode {
    if repeating {
        ExtendMode::Repeat
    } else {
        ExtendMode::Clamp
    }
}
/// Returns the the distance to the nearest or farthest corner depending on the comperator.
fn distance_to_corner<F>(size: &Size2D<Au>, center: &Point2D<Au>, cmp: F) -> Au
where
    F: Fn(Au, Au) -> Au,
{
    let dist = distance_to_sides(size, center, cmp);
    Au::from_f32_px(dist.width.to_f32_px().hypot(dist.height.to_f32_px()))
}

/// Returns the distance to the nearest or farthest sides depending on the comparator.
///
/// The first return value is horizontal distance the second vertical distance.
fn distance_to_sides<F>(size: &Size2D<Au>, center: &Point2D<Au>, cmp: F) -> Size2D<Au>
where
    F: Fn(Au, Au) -> Au,
{
    let top_side = center.y;
    let right_side = size.width - center.x;
    let bottom_side = size.height - center.y;
    let left_side = center.x;
    Size2D::new(cmp(left_side, right_side), cmp(top_side, bottom_side))
}

fn position_to_offset(position: LengthOrPercentage, total_length: Au) -> f32 {
    if total_length == Au(0) {
        return 0.0;
    }
    match position {
        LengthOrPercentage::Length(l) => l.to_i32_au() as f32 / total_length.0 as f32,
        LengthOrPercentage::Percentage(percentage) => percentage.0 as f32,
        LengthOrPercentage::Calc(calc) => {
            calc.to_used_value(Some(total_length)).unwrap().0 as f32 / total_length.0 as f32
        },
    }
}

pub fn linear(
    style: &ComputedValues,
    size: Size2D<Au>,
    stops: &[GradientItem],
    direction: LineDirection,
    repeating: bool,
) -> (Gradient, Vec<GradientStop>) {
    let angle = match direction {
        LineDirection::Angle(angle) => angle.radians(),
        LineDirection::Horizontal(x) => match x {
            X::Left => Angle::from_degrees(270.).radians(),
            X::Right => Angle::from_degrees(90.).radians(),
        },
        LineDirection::Vertical(y) => match y {
            Y::Top => Angle::from_degrees(0.).radians(),
            Y::Bottom => Angle::from_degrees(180.).radians(),
        },
        LineDirection::Corner(horizontal, vertical) => {
            // This the angle for one of the diagonals of the box. Our angle
            // will either be this one, this one + PI, or one of the other
            // two perpendicular angles.
            let atan = (size.height.to_f32_px() / size.width.to_f32_px()).atan();
            match (horizontal, vertical) {
                (X::Right, Y::Bottom) => ::std::f32::consts::PI - atan,
                (X::Left, Y::Bottom) => ::std::f32::consts::PI + atan,
                (X::Right, Y::Top) => atan,
                (X::Left, Y::Top) => -atan,
            }
        },
    };

    // Get correct gradient line length, based on:
    // https://drafts.csswg.org/css-images-3/#linear-gradients
    let dir = Point2D::new(angle.sin(), -angle.cos());

    let line_length =
        (dir.x * size.width.to_f32_px()).abs() + (dir.y * size.height.to_f32_px()).abs();

    let inv_dir_length = 1.0 / (dir.x * dir.x + dir.y * dir.y).sqrt();

    // This is the vector between the center and the ending point; i.e. half
    // of the distance between the starting point and the ending point.
    let delta = Vector2D::new(
        Au::from_f32_px(dir.x * inv_dir_length * line_length / 2.0),
        Au::from_f32_px(dir.y * inv_dir_length * line_length / 2.0),
    );

    // This is the length of the gradient line.
    let length = Au::from_f32_px((delta.x.to_f32_px() * 2.0).hypot(delta.y.to_f32_px() * 2.0));

    let mut builder = convert_gradient_stops(style, stops, length);

    let center = Point2D::new(size.width / 2, size.height / 2);

    (
        builder.gradient(
            (center - delta).to_layout(),
            (center + delta).to_layout(),
            extend_mode(repeating),
        ),
        builder.stops().to_vec(),
    )
}

pub fn radial(
    style: &ComputedValues,
    size: Size2D<Au>,
    stops: &[GradientItem],
    shape: EndingShape,
    center: Position,
    repeating: bool,
) -> (RadialGradient, Vec<GradientStop>) {
    let center = Point2D::new(
        center.horizontal.to_used_value(size.width),
        center.vertical.to_used_value(size.height),
    );
    let radius = match shape {
        GenericEndingShape::Circle(Circle::Radius(length)) => {
            let length = Au::from(length);
            Size2D::new(length, length)
        },
        GenericEndingShape::Circle(Circle::Extent(extent)) => {
            circle_size_keyword(extent, &size, &center)
        },
        GenericEndingShape::Ellipse(Ellipse::Radii(x, y)) => {
            Size2D::new(x.to_used_value(size.width), y.to_used_value(size.height))
        },
        GenericEndingShape::Ellipse(Ellipse::Extent(extent)) => {
            ellipse_size_keyword(extent, &size, &center)
        },
    };

    let mut builder = convert_gradient_stops(style, stops, radius.width);
    (
        builder.radial_gradient(
            center.to_layout(),
            radius.to_layout(),
            extend_mode(repeating),
        ),
        builder.stops().to_vec(),
    )
}
<commit_msg>Auto merge of #22164 - pyfisch:into-stops, r=emilio<commit_after>/* 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/. */

// FIXME(rust-lang/rust#26264): Remove GenericEndingShape and GenericGradientItem.

use app_units::Au;
use crate::display_list::ToLayout;
use euclid::{Point2D, Size2D, Vector2D};
use style::properties::ComputedValues;
use style::values::computed::image::{EndingShape, LineDirection};
use style::values::computed::{Angle, GradientItem, LengthOrPercentage, Percentage, Position};
use style::values::generics::image::EndingShape as GenericEndingShape;
use style::values::generics::image::GradientItem as GenericGradientItem;
use style::values::generics::image::{Circle, Ellipse, ShapeExtent};
use style::values::specified::position::{X, Y};
use webrender_api::{ExtendMode, Gradient, GradientBuilder, GradientStop, RadialGradient};

/// A helper data structure for gradients.
#[derive(Clone, Copy)]
struct StopRun {
    start_offset: f32,
    end_offset: f32,
    start_index: usize,
    stop_count: usize,
}

/// Determines the radius of a circle if it was not explictly provided.
/// <https://drafts.csswg.org/css-images-3/#typedef-size>
fn circle_size_keyword(
    keyword: ShapeExtent,
    size: &Size2D<Au>,
    center: &Point2D<Au>,
) -> Size2D<Au> {
    let radius = match keyword {
        ShapeExtent::ClosestSide | ShapeExtent::Contain => {
            let dist = distance_to_sides(size, center, ::std::cmp::min);
            ::std::cmp::min(dist.width, dist.height)
        },
        ShapeExtent::FarthestSide => {
            let dist = distance_to_sides(size, center, ::std::cmp::max);
            ::std::cmp::max(dist.width, dist.height)
        },
        ShapeExtent::ClosestCorner => distance_to_corner(size, center, ::std::cmp::min),
        ShapeExtent::FarthestCorner | ShapeExtent::Cover => {
            distance_to_corner(size, center, ::std::cmp::max)
        },
    };
    Size2D::new(radius, radius)
}

/// Returns the radius for an ellipse with the same ratio as if it was matched to the sides.
fn ellipse_radius<F>(size: &Size2D<Au>, center: &Point2D<Au>, cmp: F) -> Size2D<Au>
where
    F: Fn(Au, Au) -> Au,
{
    let dist = distance_to_sides(size, center, cmp);
    Size2D::new(
        dist.width.scale_by(::std::f32::consts::FRAC_1_SQRT_2 * 2.0),
        dist.height
            .scale_by(::std::f32::consts::FRAC_1_SQRT_2 * 2.0),
    )
}

/// Determines the radius of an ellipse if it was not explictly provided.
/// <https://drafts.csswg.org/css-images-3/#typedef-size>
fn ellipse_size_keyword(
    keyword: ShapeExtent,
    size: &Size2D<Au>,
    center: &Point2D<Au>,
) -> Size2D<Au> {
    match keyword {
        ShapeExtent::ClosestSide | ShapeExtent::Contain => {
            distance_to_sides(size, center, ::std::cmp::min)
        },
        ShapeExtent::FarthestSide => distance_to_sides(size, center, ::std::cmp::max),
        ShapeExtent::ClosestCorner => ellipse_radius(size, center, ::std::cmp::min),
        ShapeExtent::FarthestCorner | ShapeExtent::Cover => {
            ellipse_radius(size, center, ::std::cmp::max)
        },
    }
}

fn convert_gradient_stops(
    style: &ComputedValues,
    gradient_items: &[GradientItem],
    total_length: Au,
) -> GradientBuilder {
    // Determine the position of each stop per CSS-IMAGES § 3.4.

    // Only keep the color stops, discard the color interpolation hints.
    let mut stop_items = gradient_items
        .iter()
        .filter_map(|item| match *item {
            GenericGradientItem::ColorStop(ref stop) => Some(*stop),
            _ => None,
        })
        .collect::<Vec<_>>();

    assert!(stop_items.len() >= 2);

    // Run the algorithm from
    // https://drafts.csswg.org/css-images-3/#color-stop-syntax

    // Step 1:
    // If the first color stop does not have a position, set its position to 0%.
    {
        let first = stop_items.first_mut().unwrap();
        if first.position.is_none() {
            first.position = Some(LengthOrPercentage::Percentage(Percentage(0.0)));
        }
    }
    // If the last color stop does not have a position, set its position to 100%.
    {
        let last = stop_items.last_mut().unwrap();
        if last.position.is_none() {
            last.position = Some(LengthOrPercentage::Percentage(Percentage(1.0)));
        }
    }

    // Step 2: Move any stops placed before earlier stops to the
    // same position as the preceding stop.
    let mut last_stop_position = stop_items.first().unwrap().position.unwrap();
    for stop in stop_items.iter_mut().skip(1) {
        if let Some(pos) = stop.position {
            if position_to_offset(last_stop_position, total_length) >
                position_to_offset(pos, total_length)
            {
                stop.position = Some(last_stop_position);
            }
            last_stop_position = stop.position.unwrap();
        }
    }

    // Step 3: Evenly space stops without position.
    let mut stops = GradientBuilder::new();
    let mut stop_run = None;
    for (i, stop) in stop_items.iter().enumerate() {
        let offset = match stop.position {
            None => {
                if stop_run.is_none() {
                    // Initialize a new stop run.
                    // `unwrap()` here should never fail because this is the beginning of
                    // a stop run, which is always bounded by a length or percentage.
                    let start_offset =
                        position_to_offset(stop_items[i - 1].position.unwrap(), total_length);
                    // `unwrap()` here should never fail because this is the end of
                    // a stop run, which is always bounded by a length or percentage.
                    let (end_index, end_stop) = stop_items[(i + 1)..]
                        .iter()
                        .enumerate()
                        .find(|&(_, ref stop)| stop.position.is_some())
                        .unwrap();
                    let end_offset = position_to_offset(end_stop.position.unwrap(), total_length);
                    stop_run = Some(StopRun {
                        start_offset,
                        end_offset,
                        start_index: i - 1,
                        stop_count: end_index,
                    })
                }

                let stop_run = stop_run.unwrap();
                let stop_run_length = stop_run.end_offset - stop_run.start_offset;
                stop_run.start_offset +
                    stop_run_length * (i - stop_run.start_index) as f32 /
                        ((2 + stop_run.stop_count) as f32)
            },
            Some(position) => {
                stop_run = None;
                position_to_offset(position, total_length)
            },
        };
        assert!(offset.is_finite());
        stops.push(GradientStop {
            offset: offset,
            color: style.resolve_color(stop.color).to_layout(),
        })
    }
    stops
}

fn extend_mode(repeating: bool) -> ExtendMode {
    if repeating {
        ExtendMode::Repeat
    } else {
        ExtendMode::Clamp
    }
}
/// Returns the the distance to the nearest or farthest corner depending on the comperator.
fn distance_to_corner<F>(size: &Size2D<Au>, center: &Point2D<Au>, cmp: F) -> Au
where
    F: Fn(Au, Au) -> Au,
{
    let dist = distance_to_sides(size, center, cmp);
    Au::from_f32_px(dist.width.to_f32_px().hypot(dist.height.to_f32_px()))
}

/// Returns the distance to the nearest or farthest sides depending on the comparator.
///
/// The first return value is horizontal distance the second vertical distance.
fn distance_to_sides<F>(size: &Size2D<Au>, center: &Point2D<Au>, cmp: F) -> Size2D<Au>
where
    F: Fn(Au, Au) -> Au,
{
    let top_side = center.y;
    let right_side = size.width - center.x;
    let bottom_side = size.height - center.y;
    let left_side = center.x;
    Size2D::new(cmp(left_side, right_side), cmp(top_side, bottom_side))
}

fn position_to_offset(position: LengthOrPercentage, total_length: Au) -> f32 {
    if total_length == Au(0) {
        return 0.0;
    }
    match position {
        LengthOrPercentage::Length(l) => l.to_i32_au() as f32 / total_length.0 as f32,
        LengthOrPercentage::Percentage(percentage) => percentage.0 as f32,
        LengthOrPercentage::Calc(calc) => {
            calc.to_used_value(Some(total_length)).unwrap().0 as f32 / total_length.0 as f32
        },
    }
}

pub fn linear(
    style: &ComputedValues,
    size: Size2D<Au>,
    stops: &[GradientItem],
    direction: LineDirection,
    repeating: bool,
) -> (Gradient, Vec<GradientStop>) {
    let angle = match direction {
        LineDirection::Angle(angle) => angle.radians(),
        LineDirection::Horizontal(x) => match x {
            X::Left => Angle::from_degrees(270.).radians(),
            X::Right => Angle::from_degrees(90.).radians(),
        },
        LineDirection::Vertical(y) => match y {
            Y::Top => Angle::from_degrees(0.).radians(),
            Y::Bottom => Angle::from_degrees(180.).radians(),
        },
        LineDirection::Corner(horizontal, vertical) => {
            // This the angle for one of the diagonals of the box. Our angle
            // will either be this one, this one + PI, or one of the other
            // two perpendicular angles.
            let atan = (size.height.to_f32_px() / size.width.to_f32_px()).atan();
            match (horizontal, vertical) {
                (X::Right, Y::Bottom) => ::std::f32::consts::PI - atan,
                (X::Left, Y::Bottom) => ::std::f32::consts::PI + atan,
                (X::Right, Y::Top) => atan,
                (X::Left, Y::Top) => -atan,
            }
        },
    };

    // Get correct gradient line length, based on:
    // https://drafts.csswg.org/css-images-3/#linear-gradients
    let dir = Point2D::new(angle.sin(), -angle.cos());

    let line_length =
        (dir.x * size.width.to_f32_px()).abs() + (dir.y * size.height.to_f32_px()).abs();

    let inv_dir_length = 1.0 / (dir.x * dir.x + dir.y * dir.y).sqrt();

    // This is the vector between the center and the ending point; i.e. half
    // of the distance between the starting point and the ending point.
    let delta = Vector2D::new(
        Au::from_f32_px(dir.x * inv_dir_length * line_length / 2.0),
        Au::from_f32_px(dir.y * inv_dir_length * line_length / 2.0),
    );

    // This is the length of the gradient line.
    let length = Au::from_f32_px((delta.x.to_f32_px() * 2.0).hypot(delta.y.to_f32_px() * 2.0));

    let mut builder = convert_gradient_stops(style, stops, length);

    let center = Point2D::new(size.width / 2, size.height / 2);

    (
        builder.gradient(
            (center - delta).to_layout(),
            (center + delta).to_layout(),
            extend_mode(repeating),
        ),
        builder.into_stops(),
    )
}

pub fn radial(
    style: &ComputedValues,
    size: Size2D<Au>,
    stops: &[GradientItem],
    shape: EndingShape,
    center: Position,
    repeating: bool,
) -> (RadialGradient, Vec<GradientStop>) {
    let center = Point2D::new(
        center.horizontal.to_used_value(size.width),
        center.vertical.to_used_value(size.height),
    );
    let radius = match shape {
        GenericEndingShape::Circle(Circle::Radius(length)) => {
            let length = Au::from(length);
            Size2D::new(length, length)
        },
        GenericEndingShape::Circle(Circle::Extent(extent)) => {
            circle_size_keyword(extent, &size, &center)
        },
        GenericEndingShape::Ellipse(Ellipse::Radii(x, y)) => {
            Size2D::new(x.to_used_value(size.width), y.to_used_value(size.height))
        },
        GenericEndingShape::Ellipse(Ellipse::Extent(extent)) => {
            ellipse_size_keyword(extent, &size, &center)
        },
    };

    let mut builder = convert_gradient_stops(style, stops, radius.width);
    (
        builder.radial_gradient(
            center.to_layout(),
            radius.to_layout(),
            extend_mode(repeating),
        ),
        builder.into_stops(),
    )
}
<|endoftext|>