Auto merge of #7644 - pcwalton:carrot, r=mbrubeck

layout: Draw the insertion point in input elements.

Known issues:

* The caret doesn't show up if there's no text present, because we don't create text runs in that case. This should be a followup.

* Text runs don't support decomposing ligatures into their constituent subglyphs for advance computation, so the caret won't appear inside a ligature. This is a text run bug.

r? @mbrubeck

<!-- Reviewable:start -->
[<img src="https://reviewable.io/review_button.png" height=40 alt="Review on Reviewable"/>](https://reviewable.io/reviews/servo/servo/7644)
<!-- Reviewable:end -->
This commit is contained in:
bors-servo 2015-09-17 05:50:41 -06:00
commit 6cd098da30
14 changed files with 339 additions and 96 deletions

View file

@ -8,6 +8,7 @@ use euclid::point::Point2D;
use simd::u32x4; use simd::u32x4;
use std::cmp::{Ordering, PartialOrd}; use std::cmp::{Ordering, PartialOrd};
use std::fmt;
use std::mem; use std::mem;
use std::u16; use std::u16;
use std::vec::Vec; use std::vec::Vec;
@ -23,7 +24,7 @@ use util::vec::*;
/// In the uncommon case (multiple glyphs per unicode character, large glyph index/advance, or /// In the uncommon case (multiple glyphs per unicode character, large glyph index/advance, or
/// glyph offsets), we pack the glyph count into GlyphEntry, and store the other glyph information /// glyph offsets), we pack the glyph count into GlyphEntry, and store the other glyph information
/// in DetailedGlyphStore. /// in DetailedGlyphStore.
#[derive(Clone, Debug, Copy, Deserialize, Serialize)] #[derive(Clone, Debug, Copy, Deserialize, Serialize, PartialEq)]
struct GlyphEntry { struct GlyphEntry {
value: u32, value: u32,
} }
@ -72,6 +73,10 @@ impl GlyphEntry {
GlyphEntry::new(glyph_count as u32) GlyphEntry::new(glyph_count as u32)
} }
fn is_initial(&self) -> bool {
*self == GlyphEntry::initial()
}
} }
/// The id of a particular glyph within a font /// The id of a particular glyph within a font
@ -524,11 +529,14 @@ impl<'a> GlyphStore {
} }
// used when a character index has no associated glyph---for example, a ligature continuation. // used when a character index has no associated glyph---for example, a ligature continuation.
pub fn add_nonglyph_for_char_index(&mut self, i: CharIndex, cluster_start: bool, ligature_start: bool) { pub fn add_nonglyph_for_char_index(&mut self,
i: CharIndex,
cluster_start: bool,
ligature_start: bool) {
assert!(i < self.char_len()); assert!(i < self.char_len());
let entry = GlyphEntry::complex(cluster_start, ligature_start, 0); let entry = GlyphEntry::complex(cluster_start, ligature_start, 0);
debug!("adding spacer for chracter without associated glyph[idx={:?}]", i); debug!("adding spacer for character without associated glyph[idx={:?}]", i);
self.entry_buffer[i.to_usize()] = entry; self.entry_buffer[i.to_usize()] = entry;
} }
@ -645,6 +653,34 @@ impl<'a> GlyphStore {
} }
} }
impl fmt::Debug for GlyphStore {
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
try!(write!(formatter, "GlyphStore:\n"));
let mut detailed_buffer = self.detail_store.detail_buffer.iter();
for entry in self.entry_buffer.iter() {
if entry.is_simple() {
try!(write!(formatter,
" simple id={:?} advance={:?}\n",
entry.id(),
entry.advance()));
continue
}
if entry.is_initial() {
continue
}
try!(write!(formatter, " complex..."));
if detailed_buffer.next().is_none() {
continue
}
try!(write!(formatter,
" detailed id={:?} advance={:?}\n",
entry.id(),
entry.advance()));
}
Ok(())
}
}
/// An iterator over the glyphs in a character range in a `GlyphStore`. /// An iterator over the glyphs in a character range in a `GlyphStore`.
pub struct GlyphIterator<'a> { pub struct GlyphIterator<'a> {
store: &'a GlyphStore, store: &'a GlyphStore,
@ -658,8 +694,10 @@ impl<'a> GlyphIterator<'a> {
#[inline(never)] #[inline(never)]
fn next_glyph_range(&mut self) -> Option<(CharIndex, GlyphInfo<'a>)> { fn next_glyph_range(&mut self) -> Option<(CharIndex, GlyphInfo<'a>)> {
match self.glyph_range.as_mut().unwrap().next() { match self.glyph_range.as_mut().unwrap().next() {
Some(j) => Some((self.char_index, Some(j) => {
GlyphInfo::Detail(self.store, self.char_index, j.get() as u16 /* ??? */))), Some((self.char_index,
GlyphInfo::Detail(self.store, self.char_index, j.get() as u16 /* ??? */)))
}
None => { None => {
// No more glyphs for current character. Try to get another. // No more glyphs for current character. Try to get another.
self.glyph_range = None; self.glyph_range = None;

View file

@ -332,12 +332,12 @@ impl Shaper {
let char_byte_start = glyph_data.byte_offset_of_glyph(glyph_span.begin()); let char_byte_start = glyph_data.byte_offset_of_glyph(glyph_span.begin());
char_byte_span.reset(char_byte_start as usize, 0); char_byte_span.reset(char_byte_start as usize, 0);
let mut glyph_spans_multiple_characters = false;
// find a range of chars corresponding to this glyph, plus // find a range of chars corresponding to this glyph, plus
// any trailing chars that do not have associated glyphs. // any trailing chars that do not have associated glyphs.
while char_byte_span.end() < byte_max { while char_byte_span.end() < byte_max {
let range = text.char_range_at(char_byte_span.end()); let range = text.char_range_at(char_byte_span.end());
drop(range.ch);
char_byte_span.extend_to(range.next); char_byte_span.extend_to(range.next);
debug!("Processing char byte span: off={}, len={} for glyph idx={}", debug!("Processing char byte span: off={}, len={} for glyph idx={}",
@ -348,8 +348,8 @@ impl Shaper {
debug!("Extending char byte span to include byte offset={} with no associated \ debug!("Extending char byte span to include byte offset={} with no associated \
glyph", char_byte_span.end()); glyph", char_byte_span.end());
let range = text.char_range_at(char_byte_span.end()); let range = text.char_range_at(char_byte_span.end());
drop(range.ch);
char_byte_span.extend_to(range.next); char_byte_span.extend_to(range.next);
glyph_spans_multiple_characters = true;
} }
// extend glyph range to max glyph index covered by char_span, // extend glyph range to max glyph index covered by char_span,
@ -435,7 +435,7 @@ impl Shaper {
covered_byte_span.extend_to(cmp::min(end, byte_max)); covered_byte_span.extend_to(cmp::min(end, byte_max));
// fast path: 1-to-1 mapping of single char and single glyph. // fast path: 1-to-1 mapping of single char and single glyph.
if glyph_span.length() == 1 { if glyph_span.length() == 1 && !glyph_spans_multiple_characters {
// TODO(Issue #214): cluster ranges need to be computed before // TODO(Issue #214): cluster ranges need to be computed before
// shaping, and then consulted here. // shaping, and then consulted here.
// for now, just pretend that every character is a cluster start. // for now, just pretend that every character is a cluster start.
@ -480,7 +480,6 @@ impl Shaper {
let mut i = covered_byte_span.begin(); let mut i = covered_byte_span.begin();
loop { loop {
let range = text.char_range_at(i); let range = text.char_range_at(i);
drop(range.ch);
i = range.next; i = range.next;
if i >= covered_byte_span.end() { break; } if i >= covered_byte_span.end() { break; }
char_idx = char_idx + char_step; char_idx = char_idx + char_step;

View file

@ -41,7 +41,7 @@ use table_rowgroup::TableRowGroupFlow;
use table_wrapper::TableWrapperFlow; use table_wrapper::TableWrapperFlow;
use text::TextRunScanner; use text::TextRunScanner;
use traversal::PostorderNodeMutTraversal; use traversal::PostorderNodeMutTraversal;
use wrapper::{PseudoElementType, ThreadSafeLayoutNode}; use wrapper::{PseudoElementType, TextContent, ThreadSafeLayoutNode};
use gfx::display_list::OpaqueNode; use gfx::display_list::OpaqueNode;
use script::dom::characterdata::CharacterDataTypeId; use script::dom::characterdata::CharacterDataTypeId;
@ -583,7 +583,7 @@ impl<'a> FlowConstructor<'a> {
// Add whitespace results. They will be stripped out later on when // Add whitespace results. They will be stripped out later on when
// between block elements, and retained when between inline elements. // between block elements, and retained when between inline elements.
let fragment_info = SpecificFragmentInfo::UnscannedText( let fragment_info = SpecificFragmentInfo::UnscannedText(
UnscannedTextFragmentInfo::from_text(" ".to_owned())); UnscannedTextFragmentInfo::new(" ".to_owned(), None));
properties::modify_style_for_replaced_content(&mut whitespace_style); properties::modify_style_for_replaced_content(&mut whitespace_style);
properties::modify_style_for_text(&mut whitespace_style); properties::modify_style_for_text(&mut whitespace_style);
let fragment = Fragment::from_opaque_node_and_style(whitespace_node, let fragment = Fragment::from_opaque_node_and_style(whitespace_node,
@ -714,31 +714,44 @@ impl<'a> FlowConstructor<'a> {
return return
} }
let insertion_point = node.insertion_point();
let mut style = (*style).clone(); let mut style = (*style).clone();
properties::modify_style_for_text(&mut style); properties::modify_style_for_text(&mut style);
for content_item in text_content {
let specific = match content_item {
ContentItem::String(string) => {
let info = UnscannedTextFragmentInfo::from_text(string);
SpecificFragmentInfo::UnscannedText(info)
}
content_item => {
let content_item = box GeneratedContentInfo::ContentItem(content_item);
SpecificFragmentInfo::GeneratedContent(content_item)
}
};
fragments.fragments match text_content {
.push_back(Fragment::from_opaque_node_and_style(node.opaque(), TextContent::Text(string) => {
node.get_pseudo_element_type() let info = UnscannedTextFragmentInfo::new(string, insertion_point);
.strip(), let specific_fragment_info = SpecificFragmentInfo::UnscannedText(info);
style.clone(), fragments.fragments.push_back(Fragment::from_opaque_node_and_style(
node.restyle_damage(), node.opaque(),
specific)) node.get_pseudo_element_type().strip(),
style.clone(),
node.restyle_damage(),
specific_fragment_info))
}
TextContent::GeneratedContent(content_items) => {
for content_item in content_items.into_iter() {
let specific_fragment_info = match content_item {
ContentItem::String(string) => {
let info = UnscannedTextFragmentInfo::new(string, None);
SpecificFragmentInfo::UnscannedText(info)
}
content_item => {
let content_item = box GeneratedContentInfo::ContentItem(content_item);
SpecificFragmentInfo::GeneratedContent(content_item)
}
};
fragments.fragments.push_back(Fragment::from_opaque_node_and_style(
node.opaque(),
node.get_pseudo_element_type().strip(),
style.clone(),
node.restyle_damage(),
specific_fragment_info))
}
}
} }
} }
/// Builds a flow for a node with `display: block`. This yields a `BlockFlow` with possibly /// Builds a flow for a node with `display: block`. This yields a `BlockFlow` with possibly
/// other `BlockFlow`s or `InlineFlow`s underneath it, depending on whether {ib} splits needed /// other `BlockFlow`s or `InlineFlow`s underneath it, depending on whether {ib} splits needed
/// to happen. /// to happen.
@ -851,7 +864,7 @@ impl<'a> FlowConstructor<'a> {
whitespace_damage)) => { whitespace_damage)) => {
// Instantiate the whitespace fragment. // Instantiate the whitespace fragment.
let fragment_info = SpecificFragmentInfo::UnscannedText( let fragment_info = SpecificFragmentInfo::UnscannedText(
UnscannedTextFragmentInfo::from_text(" ".to_owned())); UnscannedTextFragmentInfo::new(" ".to_owned(), None));
properties::modify_style_for_replaced_content(&mut whitespace_style); properties::modify_style_for_replaced_content(&mut whitespace_style);
properties::modify_style_for_text(&mut whitespace_style); properties::modify_style_for_text(&mut whitespace_style);
let fragment = Fragment::from_opaque_node_and_style(whitespace_node, let fragment = Fragment::from_opaque_node_and_style(whitespace_node,
@ -1199,7 +1212,7 @@ impl<'a> FlowConstructor<'a> {
unscanned_marker_fragments.push_back(Fragment::new( unscanned_marker_fragments.push_back(Fragment::new(
node, node,
SpecificFragmentInfo::UnscannedText( SpecificFragmentInfo::UnscannedText(
UnscannedTextFragmentInfo::from_text(text)))); UnscannedTextFragmentInfo::new(text, None))));
let marker_fragments = TextRunScanner::new().scan_for_runs( let marker_fragments = TextRunScanner::new().scan_for_runs(
&mut self.layout_context.font_context(), &mut self.layout_context.font_context(),
unscanned_marker_fragments); unscanned_marker_fragments);
@ -1793,7 +1806,7 @@ fn control_chars_to_fragment(node: &InlineFragmentNodeInfo,
restyle_damage: RestyleDamage) restyle_damage: RestyleDamage)
-> Fragment { -> Fragment {
let info = SpecificFragmentInfo::UnscannedText( let info = SpecificFragmentInfo::UnscannedText(
UnscannedTextFragmentInfo::from_text(String::from(text))); UnscannedTextFragmentInfo::new(String::from(text), None));
let mut style = node.style.clone(); let mut style = node.style.clone();
properties::modify_style_for_text(&mut style); properties::modify_style_for_text(&mut style);
Fragment::from_opaque_node_and_style(node.address, Fragment::from_opaque_node_and_style(node.address,

View file

@ -204,9 +204,8 @@ impl<'a> LayoutContext<'a> {
} }
// Not yet requested, async mode - request image from the cache // Not yet requested, async mode - request image from the cache
(ImageState::NotRequested, false) => { (ImageState::NotRequested, false) => {
self.shared.image_cache_task.request_image(url, self.shared.image_cache_task
self.shared.image_cache_sender.clone(), .request_image(url, self.shared.image_cache_sender.clone(), None);
None);
None None
} }
// Image has been requested, is still pending. Return no image // Image has been requested, is still pending. Return no image
@ -218,3 +217,4 @@ impl<'a> LayoutContext<'a> {
} }
} }
} }

View file

@ -34,6 +34,7 @@ use gfx::display_list::{GradientStop, ImageDisplayItem, LineDisplayItem};
use gfx::display_list::{OpaqueNode, SolidColorDisplayItem}; use gfx::display_list::{OpaqueNode, SolidColorDisplayItem};
use gfx::display_list::{StackingContext, TextDisplayItem, TextOrientation}; use gfx::display_list::{StackingContext, TextDisplayItem, TextOrientation};
use gfx::paint_task::THREAD_TINT_COLORS; use gfx::paint_task::THREAD_TINT_COLORS;
use gfx::text::glyph::CharIndex;
use gfx_traits::color; use gfx_traits::color;
use ipc_channel::ipc::{self, IpcSharedMemory}; use ipc_channel::ipc::{self, IpcSharedMemory};
use msg::compositor_msg::{ScrollPolicy, LayerId}; use msg::compositor_msg::{ScrollPolicy, LayerId};
@ -60,15 +61,19 @@ use style::values::computed::{LengthOrNone, LengthOrPercentage, LengthOrPercenta
use style::values::specified::{AngleOrCorner, HorizontalDirection, VerticalDirection}; use style::values::specified::{AngleOrCorner, HorizontalDirection, VerticalDirection};
use url::Url; use url::Url;
use util::cursor::Cursor; use util::cursor::Cursor;
use util::geometry::{Au, ZERO_POINT}; use util::geometry::{AU_PER_PX, Au, ZERO_POINT};
use util::logical_geometry::{LogicalPoint, LogicalRect, LogicalSize, WritingMode}; use util::logical_geometry::{LogicalPoint, LogicalRect, LogicalSize, WritingMode};
use util::opts; use util::opts;
use util::range::Range;
/// The fake fragment ID we use to indicate the inner display list for `overflow: scroll`. /// The fake fragment ID we use to indicate the inner display list for `overflow: scroll`.
/// ///
/// FIXME(pcwalton): This is pretty ugly. Consider modifying `LayerId` somehow. /// FIXME(pcwalton): This is pretty ugly. Consider modifying `LayerId` somehow.
const FAKE_FRAGMENT_ID_FOR_OVERFLOW_SCROLL: u32 = 1000000; const FAKE_FRAGMENT_ID_FOR_OVERFLOW_SCROLL: u32 = 1000000;
/// The logical width of an insertion point: at the moment, a one-pixel-wide line.
const INSERTION_POINT_LOGICAL_WIDTH: Au = Au(1 * AU_PER_PX);
/// Whether a stacking context needs a layer or not. /// Whether a stacking context needs a layer or not.
pub enum StackingContextLayerNecessity { pub enum StackingContextLayerNecessity {
Always(LayerId, ScrollPolicy), Always(LayerId, ScrollPolicy),
@ -229,6 +234,14 @@ pub trait FragmentDisplayListBuilding {
stacking_relative_border_box: &Rect<Au>) stacking_relative_border_box: &Rect<Au>)
-> ClippingRegion; -> ClippingRegion;
/// Builds the display items necessary to paint the selection and/or caret for this fragment,
/// if any.
fn build_display_items_for_selection_if_necessary(&self,
display_list: &mut DisplayList,
stacking_relative_border_box: &Rect<Au>,
level: StackingLevel,
clip: &ClippingRegion);
/// Creates the text display item for one text fragment. This can be called multiple times for /// Creates the text display item for one text fragment. This can be called multiple times for
/// one fragment if there are text shadows. /// one fragment if there are text shadows.
/// ///
@ -882,6 +895,50 @@ impl FragmentDisplayListBuilding for Fragment {
(*parent_clip).clone().intersect_rect(&Rect::new(clip_origin, clip_size)) (*parent_clip).clone().intersect_rect(&Rect::new(clip_origin, clip_size))
} }
fn build_display_items_for_selection_if_necessary(&self,
display_list: &mut DisplayList,
stacking_relative_border_box: &Rect<Au>,
level: StackingLevel,
clip: &ClippingRegion) {
let scanned_text_fragment_info = match self.specific {
SpecificFragmentInfo::ScannedText(ref scanned_text_fragment_info) => {
scanned_text_fragment_info
}
_ => return,
};
let insertion_point_index = match scanned_text_fragment_info.insertion_point {
Some(insertion_point_index) => insertion_point_index,
None => return,
};
let range = Range::new(CharIndex(0), insertion_point_index);
let advance = scanned_text_fragment_info.run.advance_for_range(&range);
let insertion_point_bounds;
let cursor;
if !self.style.writing_mode.is_vertical() {
insertion_point_bounds =
Rect::new(Point2D::new(stacking_relative_border_box.origin.x + advance,
stacking_relative_border_box.origin.y),
Size2D::new(INSERTION_POINT_LOGICAL_WIDTH,
stacking_relative_border_box.size.height));
cursor = Cursor::TextCursor;
} else {
insertion_point_bounds =
Rect::new(Point2D::new(stacking_relative_border_box.origin.x,
stacking_relative_border_box.origin.y + advance),
Size2D::new(stacking_relative_border_box.size.width,
INSERTION_POINT_LOGICAL_WIDTH));
cursor = Cursor::VerticalTextCursor;
};
display_list.push(DisplayItem::SolidColorClass(box SolidColorDisplayItem {
base: BaseDisplayItem::new(insertion_point_bounds,
DisplayItemMetadata::new(self.node, &*self.style, cursor),
clip.clone()),
color: self.style().get_color().color.to_gfx_color(),
}), level);
}
fn build_display_list(&mut self, fn build_display_list(&mut self,
display_list: &mut DisplayList, display_list: &mut DisplayList,
layout_context: &LayoutContext, layout_context: &LayoutContext,
@ -967,6 +1024,7 @@ impl FragmentDisplayListBuilding for Fragment {
&clip); &clip);
} }
} }
if !self.is_scanned_text_fragment() { if !self.is_scanned_text_fragment() {
self.build_display_list_for_box_shadow_if_applicable(&*self.style, self.build_display_list_for_box_shadow_if_applicable(&*self.style,
display_list, display_list,
@ -991,6 +1049,12 @@ impl FragmentDisplayListBuilding for Fragment {
&stacking_relative_border_box, &stacking_relative_border_box,
&clip); &clip);
} }
// Paint the selection point if necessary.
self.build_display_items_for_selection_if_necessary(display_list,
&stacking_relative_border_box,
level,
&clip);
} }
// Create special per-fragment-type display items. // Create special per-fragment-type display items.
@ -2044,3 +2108,4 @@ pub enum StackingContextCreationMode {
OuterScrollWrapper, OuterScrollWrapper,
InnerScrollWrapper, InnerScrollWrapper,
} }

View file

@ -651,6 +651,11 @@ pub struct ScannedTextFragmentInfo {
/// The intrinsic size of the text fragment. /// The intrinsic size of the text fragment.
pub content_size: LogicalSize<Au>, pub content_size: LogicalSize<Au>,
/// The position of the insertion point in characters, if any.
///
/// TODO(pcwalton): Make this a range.
pub insertion_point: Option<CharIndex>,
/// The range within the above text run that this represents. /// The range within the above text run that this represents.
pub range: Range<CharIndex>, pub range: Range<CharIndex>,
@ -669,11 +674,13 @@ impl ScannedTextFragmentInfo {
pub fn new(run: Arc<TextRun>, pub fn new(run: Arc<TextRun>,
range: Range<CharIndex>, range: Range<CharIndex>,
content_size: LogicalSize<Au>, content_size: LogicalSize<Au>,
insertion_point: &Option<CharIndex>,
requires_line_break_afterward_if_wrapping_on_newlines: bool) requires_line_break_afterward_if_wrapping_on_newlines: bool)
-> ScannedTextFragmentInfo { -> ScannedTextFragmentInfo {
ScannedTextFragmentInfo { ScannedTextFragmentInfo {
run: run, run: run,
range: range, range: range,
insertion_point: *insertion_point,
content_size: content_size, content_size: content_size,
range_end_including_stripped_whitespace: range.end(), range_end_including_stripped_whitespace: range.end(),
requires_line_break_afterward_if_wrapping_on_newlines: requires_line_break_afterward_if_wrapping_on_newlines:
@ -726,14 +733,20 @@ pub struct TruncationResult {
pub struct UnscannedTextFragmentInfo { pub struct UnscannedTextFragmentInfo {
/// The text inside the fragment. /// The text inside the fragment.
pub text: Box<str>, pub text: Box<str>,
/// The position of the insertion point, if any.
///
/// TODO(pcwalton): Make this a range.
pub insertion_point: Option<CharIndex>,
} }
impl UnscannedTextFragmentInfo { impl UnscannedTextFragmentInfo {
/// Creates a new instance of `UnscannedTextFragmentInfo` from the given text. /// Creates a new instance of `UnscannedTextFragmentInfo` from the given text.
#[inline] #[inline]
pub fn from_text(text: String) -> UnscannedTextFragmentInfo { pub fn new(text: String, insertion_point: Option<CharIndex>) -> UnscannedTextFragmentInfo {
UnscannedTextFragmentInfo { UnscannedTextFragmentInfo {
text: text.into_boxed_str(), text: text.into_boxed_str(),
insertion_point: insertion_point,
} }
} }
} }
@ -843,10 +856,12 @@ impl Fragment {
self.border_box.size.block); self.border_box.size.block);
let requires_line_break_afterward_if_wrapping_on_newlines = let requires_line_break_afterward_if_wrapping_on_newlines =
self.requires_line_break_afterward_if_wrapping_on_newlines(); self.requires_line_break_afterward_if_wrapping_on_newlines();
// FIXME(pcwalton): This should modify the insertion point as necessary.
let info = box ScannedTextFragmentInfo::new( let info = box ScannedTextFragmentInfo::new(
text_run, text_run,
split.range, split.range,
size, size,
&None,
requires_line_break_afterward_if_wrapping_on_newlines); requires_line_break_afterward_if_wrapping_on_newlines);
self.transform(size, SpecificFragmentInfo::ScannedText(info)) self.transform(size, SpecificFragmentInfo::ScannedText(info))
} }
@ -856,8 +871,8 @@ impl Fragment {
let mut unscanned_ellipsis_fragments = LinkedList::new(); let mut unscanned_ellipsis_fragments = LinkedList::new();
unscanned_ellipsis_fragments.push_back(self.transform( unscanned_ellipsis_fragments.push_back(self.transform(
self.border_box.size, self.border_box.size,
SpecificFragmentInfo::UnscannedText(UnscannedTextFragmentInfo::from_text( SpecificFragmentInfo::UnscannedText(UnscannedTextFragmentInfo::new("".to_owned(),
"".to_owned())))); None))));
let ellipsis_fragments = TextRunScanner::new().scan_for_runs(&mut layout_context.font_context(), let ellipsis_fragments = TextRunScanner::new().scan_for_runs(&mut layout_context.font_context(),
unscanned_ellipsis_fragments); unscanned_ellipsis_fragments);
debug_assert!(ellipsis_fragments.len() == 1); debug_assert!(ellipsis_fragments.len() == 1);

View file

@ -428,7 +428,7 @@ fn render_text(layout_context: &LayoutContext,
string: String) string: String)
-> Option<SpecificFragmentInfo> { -> Option<SpecificFragmentInfo> {
let mut fragments = LinkedList::new(); let mut fragments = LinkedList::new();
let info = SpecificFragmentInfo::UnscannedText(UnscannedTextFragmentInfo::from_text(string)); let info = SpecificFragmentInfo::UnscannedText(UnscannedTextFragmentInfo::new(string, None));
fragments.push_back(Fragment::from_opaque_node_and_style(node, fragments.push_back(Fragment::from_opaque_node_and_style(node,
pseudo, pseudo,
style, style,

View file

@ -20,8 +20,9 @@ use incremental::{LayoutDamageComputation, REFLOW, REFLOW_ENTIRE_DOCUMENT, REPAI
use layout_debug; use layout_debug;
use opaque_node::OpaqueNodeMethods; use opaque_node::OpaqueNodeMethods;
use parallel::{self, WorkQueueData}; use parallel::{self, WorkQueueData};
use query::{LayoutRPCImpl, process_content_box_request, process_content_boxes_request, MarginPadding, Side}; use query::{LayoutRPCImpl, process_content_box_request, process_content_boxes_request};
use query::{MarginRetrievingFragmentBorderBoxIterator, PositionProperty, PositionRetrievingFragmentBorderBoxIterator}; use query::{MarginPadding, MarginRetrievingFragmentBorderBoxIterator, PositionProperty};
use query::{PositionRetrievingFragmentBorderBoxIterator, Side};
use sequential; use sequential;
use wrapper::LayoutNode; use wrapper::LayoutNode;
@ -244,7 +245,9 @@ impl LayoutTaskFactory for LayoutTask {
mem_profiler_chan: mem::ProfilerChan, mem_profiler_chan: mem::ProfilerChan,
shutdown_chan: Sender<()>) { shutdown_chan: Sender<()>) {
let ConstellationChan(con_chan) = constellation_chan.clone(); let ConstellationChan(con_chan) = constellation_chan.clone();
spawn_named_with_send_on_failure(format!("LayoutTask {:?}", id), task_state::LAYOUT, move || { spawn_named_with_send_on_failure(format!("LayoutTask {:?}", id),
task_state::LAYOUT,
move || {
{ // Ensures layout task is destroyed before we send shutdown message { // Ensures layout task is destroyed before we send shutdown message
let sender = chan.sender(); let sender = chan.sender();
let layout_chan = LayoutChan(sender); let layout_chan = LayoutChan(sender);
@ -561,7 +564,11 @@ impl LayoutTask {
self.handle_add_stylesheet(sheet, mq, possibly_locked_rw_data) self.handle_add_stylesheet(sheet, mq, possibly_locked_rw_data)
} }
Msg::LoadStylesheet(url, mq, pending, link_element) => { Msg::LoadStylesheet(url, mq, pending, link_element) => {
self.handle_load_stylesheet(url, mq, pending, link_element, possibly_locked_rw_data) self.handle_load_stylesheet(url,
mq,
pending,
link_element,
possibly_locked_rw_data)
} }
Msg::SetQuirksMode => self.handle_set_quirks_mode(possibly_locked_rw_data), Msg::SetQuirksMode => self.handle_set_quirks_mode(possibly_locked_rw_data),
Msg::GetRPC(response_chan) => { Msg::GetRPC(response_chan) => {
@ -734,7 +741,9 @@ impl LayoutTask {
Origin::Author); Origin::Author);
//TODO: mark critical subresources as blocking load as well (#5974) //TODO: mark critical subresources as blocking load as well (#5974)
self.script_chan.send(ConstellationControlMsg::StylesheetLoadComplete(self.id, url, responder)).unwrap(); self.script_chan.send(ConstellationControlMsg::StylesheetLoadComplete(self.id,
url,
responder)).unwrap();
self.handle_add_stylesheet(sheet, mq, possibly_locked_rw_data); self.handle_add_stylesheet(sheet, mq, possibly_locked_rw_data);
} }
@ -923,19 +932,22 @@ impl LayoutTask {
atom!("padding-right") => (MarginPadding::Padding, Side::Right), atom!("padding-right") => (MarginPadding::Padding, Side::Right),
_ => unreachable!() _ => unreachable!()
}; };
let requested_node: OpaqueNode = OpaqueNodeMethods::from_script_node(requested_node); let requested_node: OpaqueNode =
OpaqueNodeMethods::from_script_node(requested_node);
let mut iterator = let mut iterator =
MarginRetrievingFragmentBorderBoxIterator::new(requested_node, MarginRetrievingFragmentBorderBoxIterator::new(requested_node,
side, side,
margin_padding, margin_padding,
style.writing_mode); style.writing_mode);
sequential::iterate_through_flow_tree_fragment_border_boxes(layout_root, &mut iterator); sequential::iterate_through_flow_tree_fragment_border_boxes(layout_root,
&mut iterator);
rw_data.resolved_style_response = iterator.result.map(|r| r.to_css_string()); rw_data.resolved_style_response = iterator.result.map(|r| r.to_css_string());
}, },
atom!("bottom") | atom!("top") | atom!("right") | atom!("bottom") | atom!("top") | atom!("right") |
atom!("left") | atom!("width") | atom!("height") atom!("left") | atom!("width") | atom!("height")
if applies && positioned && style.get_box().display != display::computed_value::T::none => { if applies && positioned && style.get_box().display !=
display::computed_value::T::none => {
let layout_data = layout_node.borrow_layout_data(); let layout_data = layout_node.borrow_layout_data();
let position = layout_data.as_ref().map(|layout_data| { let position = layout_data.as_ref().map(|layout_data| {
match layout_data.data.flow_construction_result { match layout_data.data.flow_construction_result {
@ -954,17 +966,20 @@ impl LayoutTask {
atom!("height") => PositionProperty::Height, atom!("height") => PositionProperty::Height,
_ => unreachable!() _ => unreachable!()
}; };
let requested_node: OpaqueNode = OpaqueNodeMethods::from_script_node(requested_node); let requested_node: OpaqueNode =
OpaqueNodeMethods::from_script_node(requested_node);
let mut iterator = let mut iterator =
PositionRetrievingFragmentBorderBoxIterator::new(requested_node, PositionRetrievingFragmentBorderBoxIterator::new(requested_node,
property, property,
position); position);
sequential::iterate_through_flow_tree_fragment_border_boxes(layout_root, &mut iterator); sequential::iterate_through_flow_tree_fragment_border_boxes(layout_root,
&mut iterator);
rw_data.resolved_style_response = iterator.result.map(|r| r.to_css_string()); rw_data.resolved_style_response = iterator.result.map(|r| r.to_css_string());
}, },
// FIXME: implement used value computation for line-height // FIXME: implement used value computation for line-height
property => { property => {
rw_data.resolved_style_response = style.computed_value_to_string(property.as_slice()); rw_data.resolved_style_response =
style.computed_value_to_string(property.as_slice());
} }
}; };
} }
@ -1193,8 +1208,13 @@ impl LayoutTask {
process_content_boxes_request(node, &mut root_flow, &mut rw_data), process_content_boxes_request(node, &mut root_flow, &mut rw_data),
ReflowQueryType::NodeGeometryQuery(node) => ReflowQueryType::NodeGeometryQuery(node) =>
self.process_node_geometry_request(node, &mut root_flow, &mut rw_data), self.process_node_geometry_request(node, &mut root_flow, &mut rw_data),
ReflowQueryType::ResolvedStyleQuery(node, ref pseudo, ref property) => ReflowQueryType::ResolvedStyleQuery(node, ref pseudo, ref property) => {
self.process_resolved_style_request(node, pseudo, property, &mut root_flow, &mut rw_data), self.process_resolved_style_request(node,
pseudo,
property,
&mut root_flow,
&mut rw_data)
}
ReflowQueryType::OffsetParentQuery(node) => ReflowQueryType::OffsetParentQuery(node) =>
self.process_offset_parent_query(node, &mut root_flow, &mut rw_data), self.process_offset_parent_query(node, &mut root_flow, &mut rw_data),
ReflowQueryType::NoQuery => {} ReflowQueryType::NoQuery => {}
@ -1312,16 +1332,16 @@ impl LayoutTask {
self.profiler_metadata(), self.profiler_metadata(),
self.time_profiler_chan.clone(), self.time_profiler_chan.clone(),
|| { || {
if opts::get().nonincremental_layout || flow_ref::deref_mut(&mut root_flow) if opts::get().nonincremental_layout ||
.compute_layout_damage() flow_ref::deref_mut(&mut root_flow).compute_layout_damage()
.contains(REFLOW_ENTIRE_DOCUMENT) { .contains(REFLOW_ENTIRE_DOCUMENT) {
flow_ref::deref_mut(&mut root_flow).reflow_entire_document() flow_ref::deref_mut(&mut root_flow).reflow_entire_document()
} }
}); });
// Verification of the flow tree, which ensures that all nodes were either marked as leaves // Verification of the flow tree, which ensures that all nodes were either marked as
// or as non-leaves. This becomes a no-op in release builds. (It is inconsequential to // leaves or as non-leaves. This becomes a no-op in release builds. (It is
// memory safety but is a useful debugging tool.) // inconsequential to memory safety but is a useful debugging tool.)
self.verify_flow_tree(&mut root_flow); self.verify_flow_tree(&mut root_flow);
if opts::get().trace_layout { if opts::get().trace_layout {

View file

@ -176,9 +176,12 @@ impl TextRunScanner {
let (mut run_info_list, mut run_info) = (Vec::new(), RunInfo::new()); let (mut run_info_list, mut run_info) = (Vec::new(), RunInfo::new());
for (fragment_index, in_fragment) in self.clump.iter().enumerate() { for (fragment_index, in_fragment) in self.clump.iter().enumerate() {
let mut mapping = RunMapping::new(&run_info_list[..], &run_info, fragment_index); let mut mapping = RunMapping::new(&run_info_list[..], &run_info, fragment_index);
let text = match in_fragment.specific { let text;
let insertion_point;
match in_fragment.specific {
SpecificFragmentInfo::UnscannedText(ref text_fragment_info) => { SpecificFragmentInfo::UnscannedText(ref text_fragment_info) => {
&text_fragment_info.text text = &text_fragment_info.text;
insertion_point = text_fragment_info.insertion_point;
} }
_ => panic!("Expected an unscanned text fragment!"), _ => panic!("Expected an unscanned text fragment!"),
}; };
@ -208,6 +211,7 @@ impl TextRunScanner {
mapping.flush(&mut mappings, mapping.flush(&mut mappings,
&mut run_info, &mut run_info,
&**text, &**text,
insertion_point,
compression, compression,
text_transform, text_transform,
&mut last_whitespace, &mut last_whitespace,
@ -239,6 +243,7 @@ impl TextRunScanner {
mapping.flush(&mut mappings, mapping.flush(&mut mappings,
&mut run_info, &mut run_info,
&**text, &**text,
insertion_point,
compression, compression,
text_transform, text_transform,
&mut last_whitespace, &mut last_whitespace,
@ -275,7 +280,13 @@ impl TextRunScanner {
options.flags.insert(RTL_FLAG); options.flags.insert(RTL_FLAG);
} }
let mut font = fontgroup.fonts.get(run_info.font_index).unwrap().borrow_mut(); let mut font = fontgroup.fonts.get(run_info.font_index).unwrap().borrow_mut();
Arc::new(TextRun::new(&mut *font, run_info.text, &options, run_info.bidi_level)) ScannedTextRun {
run: Arc::new(TextRun::new(&mut *font,
run_info.text,
&options,
run_info.bidi_level)),
insertion_point: run_info.insertion_point,
}
}).collect::<Vec<_>>() }).collect::<Vec<_>>()
}; };
@ -296,19 +307,20 @@ impl TextRunScanner {
}; };
let mut mapping = mappings.next().unwrap(); let mut mapping = mappings.next().unwrap();
let run = runs[mapping.text_run_index].clone(); let scanned_run = runs[mapping.text_run_index].clone();
let requires_line_break_afterward_if_wrapping_on_newlines = let requires_line_break_afterward_if_wrapping_on_newlines =
run.text.char_at_reverse(mapping.byte_range.end()) == '\n'; scanned_run.run.text.char_at_reverse(mapping.byte_range.end()) == '\n';
if requires_line_break_afterward_if_wrapping_on_newlines { if requires_line_break_afterward_if_wrapping_on_newlines {
mapping.char_range.extend_by(CharIndex(-1)); mapping.char_range.extend_by(CharIndex(-1));
} }
let text_size = old_fragment.border_box.size; let text_size = old_fragment.border_box.size;
let mut new_text_fragment_info = box ScannedTextFragmentInfo::new( let mut new_text_fragment_info = box ScannedTextFragmentInfo::new(
run, scanned_run.run,
mapping.char_range, mapping.char_range,
text_size, text_size,
&scanned_run.insertion_point,
requires_line_break_afterward_if_wrapping_on_newlines); requires_line_break_afterward_if_wrapping_on_newlines);
let new_metrics = new_text_fragment_info.run.metrics_for_range(&mapping.char_range); let new_metrics = new_text_fragment_info.run.metrics_for_range(&mapping.char_range);
@ -382,6 +394,7 @@ fn split_first_fragment_at_newline_if_necessary(fragments: &mut LinkedList<Fragm
let new_fragment = { let new_fragment = {
let mut first_fragment = fragments.front_mut().unwrap(); let mut first_fragment = fragments.front_mut().unwrap();
let string_before; let string_before;
let insertion_point_before;
{ {
let unscanned_text_fragment_info = match first_fragment.specific { let unscanned_text_fragment_info = match first_fragment.specific {
SpecificFragmentInfo::UnscannedText(ref mut unscanned_text_fragment_info) => { SpecificFragmentInfo::UnscannedText(ref mut unscanned_text_fragment_info) => {
@ -403,12 +416,14 @@ fn split_first_fragment_at_newline_if_necessary(fragments: &mut LinkedList<Fragm
string_before = string_before =
unscanned_text_fragment_info.text[..(position + 1)].to_owned(); unscanned_text_fragment_info.text[..(position + 1)].to_owned();
insertion_point_before = unscanned_text_fragment_info.insertion_point;
unscanned_text_fragment_info.text = unscanned_text_fragment_info.text =
unscanned_text_fragment_info.text[(position + 1)..].to_owned().into_boxed_str(); unscanned_text_fragment_info.text[(position + 1)..].to_owned().into_boxed_str();
} }
first_fragment.transform(first_fragment.border_box.size, first_fragment.transform(first_fragment.border_box.size,
SpecificFragmentInfo::UnscannedText( SpecificFragmentInfo::UnscannedText(
UnscannedTextFragmentInfo::from_text(string_before))) UnscannedTextFragmentInfo::new(string_before,
insertion_point_before)))
}; };
fragments.push_front(new_fragment); fragments.push_front(new_fragment);
@ -418,6 +433,8 @@ fn split_first_fragment_at_newline_if_necessary(fragments: &mut LinkedList<Fragm
struct RunInfo { struct RunInfo {
/// The text that will go in this text run. /// The text that will go in this text run.
text: String, text: String,
/// The insertion point in this text run, if applicable.
insertion_point: Option<CharIndex>,
/// The index of the applicable font in the font group. /// The index of the applicable font in the font group.
font_index: usize, font_index: usize,
/// A cached copy of the number of Unicode characters in the text run. /// A cached copy of the number of Unicode characters in the text run.
@ -430,6 +447,7 @@ impl RunInfo {
fn new() -> RunInfo { fn new() -> RunInfo {
RunInfo { RunInfo {
text: String::new(), text: String::new(),
insertion_point: None,
font_index: 0, font_index: 0,
character_length: 0, character_length: 0,
bidi_level: 0, bidi_level: 0,
@ -472,6 +490,7 @@ impl RunMapping {
mappings: &mut Vec<RunMapping>, mappings: &mut Vec<RunMapping>,
run_info: &mut RunInfo, run_info: &mut RunInfo,
text: &str, text: &str,
insertion_point: Option<CharIndex>,
compression: CompressionMode, compression: CompressionMode,
text_transform: text_transform::T, text_transform: text_transform::T,
last_whitespace: &mut bool, last_whitespace: &mut bool,
@ -489,6 +508,12 @@ impl RunMapping {
old_byte_length, old_byte_length,
text_transform); text_transform);
// Record the position of the insertion point if necessary.
if let Some(insertion_point) = insertion_point {
run_info.insertion_point =
Some(CharIndex(run_info.character_length as isize + insertion_point.0))
}
run_info.character_length = run_info.character_length + character_count; run_info.character_length = run_info.character_length + character_count;
*start_position = end_position; *start_position = end_position;
@ -570,3 +595,9 @@ fn apply_style_transform_if_necessary(string: &mut String,
} }
} }
#[derive(Clone)]
struct ScannedTextRun {
run: Arc<TextRun>,
insertion_point: Option<CharIndex>,
}

View file

@ -37,6 +37,7 @@ use incremental::RestyleDamage;
use opaque_node::OpaqueNodeMethods; use opaque_node::OpaqueNodeMethods;
use gfx::display_list::OpaqueNode; use gfx::display_list::OpaqueNode;
use gfx::text::glyph::CharIndex;
use ipc_channel::ipc::IpcSender; use ipc_channel::ipc::IpcSender;
use msg::constellation_msg::{PipelineId, SubpageId}; use msg::constellation_msg::{PipelineId, SubpageId};
use script::dom::attr::AttrValue; use script::dom::attr::AttrValue;
@ -904,7 +905,7 @@ impl<'ln> ThreadSafeLayoutNode<'ln> {
/// its content. Otherwise, panics. /// its content. Otherwise, panics.
/// ///
/// FIXME(pcwalton): This might have too much copying and/or allocation. Profile this. /// FIXME(pcwalton): This might have too much copying and/or allocation. Profile this.
pub fn text_content(&self) -> Vec<ContentItem> { pub fn text_content(&self) -> TextContent {
if self.pseudo != PseudoElementType::Normal { if self.pseudo != PseudoElementType::Normal {
let layout_data_ref = self.borrow_layout_data(); let layout_data_ref = self.borrow_layout_data();
let data = &layout_data_ref.as_ref().unwrap().data; let data = &layout_data_ref.as_ref().unwrap().data;
@ -915,8 +916,10 @@ impl<'ln> ThreadSafeLayoutNode<'ln> {
&data.after_style &data.after_style
}; };
return match style.as_ref().unwrap().get_box().content { return match style.as_ref().unwrap().get_box().content {
content::T::Content(ref value) if !value.is_empty() => (*value).clone(), content::T::Content(ref value) if !value.is_empty() => {
_ => vec![], TextContent::GeneratedContent((*value).clone())
}
_ => TextContent::GeneratedContent(vec![]),
}; };
} }
@ -926,22 +929,48 @@ impl<'ln> ThreadSafeLayoutNode<'ln> {
let data = unsafe { let data = unsafe {
CharacterDataCast::from_layout_js(&text).data_for_layout().to_owned() CharacterDataCast::from_layout_js(&text).data_for_layout().to_owned()
}; };
return vec![ContentItem::String(data)]; return TextContent::Text(data);
} }
let input = HTMLInputElementCast::to_layout_js(this); let input = HTMLInputElementCast::to_layout_js(this);
if let Some(input) = input { if let Some(input) = input {
let data = unsafe { input.get_value_for_layout() }; let data = unsafe { input.get_value_for_layout() };
return vec![ContentItem::String(data)]; return TextContent::Text(data);
} }
let area = HTMLTextAreaElementCast::to_layout_js(this); let area = HTMLTextAreaElementCast::to_layout_js(this);
if let Some(area) = area { if let Some(area) = area {
let data = unsafe { area.get_value_for_layout() }; let data = unsafe { area.get_value_for_layout() };
return vec![ContentItem::String(data)]; return TextContent::Text(data);
} }
panic!("not text!") panic!("not text!")
} }
/// If the insertion point is within this node, returns it. Otherwise, returns `None`.
pub fn insertion_point(&self) -> Option<CharIndex> {
let this = unsafe {
self.get_jsmanaged()
};
let input = HTMLInputElementCast::to_layout_js(this);
if let Some(input) = input {
let insertion_point = unsafe {
input.get_insertion_point_for_layout()
};
let text = unsafe {
input.get_value_for_layout()
};
let mut character_count = 0;
for (character_index, _) in text.char_indices() {
if character_index == insertion_point.index {
return Some(CharIndex(character_count))
}
character_count += 1
}
return Some(CharIndex(character_count))
}
None
}
/// If this is an image element, returns its URL. If this is not an image element, fails. /// If this is an image element, returns its URL. If this is not an image element, fails.
/// ///
/// FIXME(pcwalton): Don't copy URLs. /// FIXME(pcwalton): Don't copy URLs.
@ -1090,3 +1119,18 @@ pub unsafe fn layout_node_from_unsafe_layout_node(node: &UnsafeLayoutNode) -> La
let (node, _) = *node; let (node, _) = *node;
mem::transmute(node) mem::transmute(node)
} }
pub enum TextContent {
Text(String),
GeneratedContent(Vec<ContentItem>),
}
impl TextContent {
pub fn is_empty(&self) -> bool {
match *self {
TextContent::Text(_) => false,
TextContent::GeneratedContent(ref content) => content.is_empty(),
}
}
}

View file

@ -27,9 +27,9 @@ use dom::node::{Node, NodeDamage, NodeTypeId};
use dom::node::{document_from_node, window_from_node}; use dom::node::{document_from_node, window_from_node};
use dom::virtualmethods::VirtualMethods; use dom::virtualmethods::VirtualMethods;
use msg::constellation_msg::ConstellationChan; use msg::constellation_msg::ConstellationChan;
use textinput::KeyReaction::{TriggerDefaultAction, DispatchInput, Nothing}; use textinput::KeyReaction::{TriggerDefaultAction, DispatchInput, Nothing, RedrawSelection};
use textinput::Lines::Single; use textinput::Lines::Single;
use textinput::TextInput; use textinput::{TextInput, TextPoint};
use string_cache::Atom; use string_cache::Atom;
use util::str::DOMString; use util::str::DOMString;
@ -144,6 +144,8 @@ pub trait LayoutHTMLInputElementHelpers {
unsafe fn get_value_for_layout(self) -> String; unsafe fn get_value_for_layout(self) -> String;
#[allow(unsafe_code)] #[allow(unsafe_code)]
unsafe fn get_size_for_layout(self) -> u32; unsafe fn get_size_for_layout(self) -> u32;
#[allow(unsafe_code)]
unsafe fn get_insertion_point_for_layout(self) -> TextPoint;
} }
pub trait RawLayoutHTMLInputElementHelpers { pub trait RawLayoutHTMLInputElementHelpers {
@ -194,6 +196,12 @@ impl LayoutHTMLInputElementHelpers for LayoutJS<HTMLInputElement> {
unsafe fn get_size_for_layout(self) -> u32 { unsafe fn get_size_for_layout(self) -> u32 {
(*self.unsafe_get()).get_size_for_layout() (*self.unsafe_get()).get_size_for_layout()
} }
#[allow(unrooted_must_root)]
#[allow(unsafe_code)]
unsafe fn get_insertion_point_for_layout(self) -> TextPoint {
(*self.unsafe_get()).textinput.borrow_for_layout().edit_point
}
} }
impl RawLayoutHTMLInputElementHelpers for HTMLInputElement { impl RawLayoutHTMLInputElementHelpers for HTMLInputElement {
@ -599,6 +607,9 @@ impl VirtualMethods for HTMLInputElement {
self.force_relayout(); self.force_relayout();
event.PreventDefault(); event.PreventDefault();
} }
RedrawSelection => {
self.force_relayout();
}
Nothing => (), Nothing => (),
} }
}); });

View file

@ -350,6 +350,9 @@ impl VirtualMethods for HTMLTextAreaElement {
self.force_relayout(); self.force_relayout();
} }
KeyReaction::RedrawSelection => {
self.force_relayout();
}
KeyReaction::Nothing => (), KeyReaction::Nothing => (),
} }
}); });

View file

@ -49,6 +49,7 @@ pub struct TextInput<T: ClipboardProvider> {
pub enum KeyReaction { pub enum KeyReaction {
TriggerDefaultAction, TriggerDefaultAction,
DispatchInput, DispatchInput,
RedrawSelection,
Nothing, Nothing,
} }
@ -366,7 +367,7 @@ impl<T: ClipboardProvider> TextInput<T> {
match key { match key {
Key::A if is_control_key(mods) => { Key::A if is_control_key(mods) => {
self.select_all(); self.select_all();
KeyReaction::Nothing KeyReaction::RedrawSelection
}, },
Key::C if is_control_key(mods) => { Key::C if is_control_key(mods) => {
if let Some(text) = self.get_selection_text() { if let Some(text) = self.get_selection_text() {
@ -397,36 +398,36 @@ impl<T: ClipboardProvider> TextInput<T> {
} }
Key::Left => { Key::Left => {
self.adjust_horizontal_by_one(Direction::Backward, maybe_select); self.adjust_horizontal_by_one(Direction::Backward, maybe_select);
KeyReaction::Nothing KeyReaction::RedrawSelection
} }
Key::Right => { Key::Right => {
self.adjust_horizontal_by_one(Direction::Forward, maybe_select); self.adjust_horizontal_by_one(Direction::Forward, maybe_select);
KeyReaction::Nothing KeyReaction::RedrawSelection
} }
Key::Up => { Key::Up => {
self.adjust_vertical(-1, maybe_select); self.adjust_vertical(-1, maybe_select);
KeyReaction::Nothing KeyReaction::RedrawSelection
} }
Key::Down => { Key::Down => {
self.adjust_vertical(1, maybe_select); self.adjust_vertical(1, maybe_select);
KeyReaction::Nothing KeyReaction::RedrawSelection
} }
Key::Enter | Key::KpEnter => self.handle_return(), Key::Enter | Key::KpEnter => self.handle_return(),
Key::Home => { Key::Home => {
self.edit_point.index = 0; self.edit_point.index = 0;
KeyReaction::Nothing KeyReaction::RedrawSelection
} }
Key::End => { Key::End => {
self.edit_point.index = self.current_line_length(); self.edit_point.index = self.current_line_length();
KeyReaction::Nothing KeyReaction::RedrawSelection
} }
Key::PageUp => { Key::PageUp => {
self.adjust_vertical(-28, maybe_select); self.adjust_vertical(-28, maybe_select);
KeyReaction::Nothing KeyReaction::RedrawSelection
} }
Key::PageDown => { Key::PageDown => {
self.adjust_vertical(28, maybe_select); self.adjust_vertical(28, maybe_select);
KeyReaction::Nothing KeyReaction::RedrawSelection
} }
Key::Tab => KeyReaction::TriggerDefaultAction, Key::Tab => KeyReaction::TriggerDefaultAction,
_ => KeyReaction::Nothing, _ => KeyReaction::Nothing,

View file

@ -56,6 +56,9 @@ pub enum ViewportPx {}
#[derive(RustcEncodable, Debug, Copy, Clone)] #[derive(RustcEncodable, Debug, Copy, Clone)]
pub enum PagePx {} pub enum PagePx {}
/// The number of app units in a pixel.
pub const AU_PER_PX: i32 = 60;
// In summary, the hierarchy of pixel units and the factors to convert from one to the next: // In summary, the hierarchy of pixel units and the factors to convert from one to the next:
// //
// DevicePixel // DevicePixel
@ -202,57 +205,57 @@ impl Au {
#[inline] #[inline]
pub fn from_px(px: i32) -> Au { pub fn from_px(px: i32) -> Au {
Au((px * 60) as i32) Au((px * AU_PER_PX) as i32)
} }
#[inline] #[inline]
pub fn from_page_px(px: Length<PagePx, f32>) -> Au { pub fn from_page_px(px: Length<PagePx, f32>) -> Au {
Au((px.get() * 60f32) as i32) Au((px.get() * (AU_PER_PX as f32)) as i32)
} }
/// Rounds this app unit down to the pixel towards zero and returns it. /// Rounds this app unit down to the pixel towards zero and returns it.
#[inline] #[inline]
pub fn to_px(self) -> i32 { pub fn to_px(self) -> i32 {
self.0 / 60 self.0 / AU_PER_PX
} }
/// Rounds this app unit down to the previous (left or top) pixel and returns it. /// Rounds this app unit down to the previous (left or top) pixel and returns it.
#[inline] #[inline]
pub fn to_prev_px(self) -> i32 { pub fn to_prev_px(self) -> i32 {
((self.0 as f64) / 60f64).floor() as i32 ((self.0 as f64) / (AU_PER_PX as f64)).floor() as i32
} }
/// Rounds this app unit up to the next (right or bottom) pixel and returns it. /// Rounds this app unit up to the next (right or bottom) pixel and returns it.
#[inline] #[inline]
pub fn to_next_px(self) -> i32 { pub fn to_next_px(self) -> i32 {
((self.0 as f64) / 60f64).ceil() as i32 ((self.0 as f64) / (AU_PER_PX as f64)).ceil() as i32
} }
#[inline] #[inline]
pub fn to_nearest_px(self) -> i32 { pub fn to_nearest_px(self) -> i32 {
((self.0 as f64) / 60f64).round() as i32 ((self.0 as f64) / (AU_PER_PX as f64)).round() as i32
} }
#[inline] #[inline]
pub fn to_f32_px(self) -> f32 { pub fn to_f32_px(self) -> f32 {
(self.0 as f32) / 60f32 (self.0 as f32) / (AU_PER_PX as f32)
} }
#[inline] #[inline]
pub fn to_f64_px(self) -> f64 { pub fn to_f64_px(self) -> f64 {
(self.0 as f64) / 60f64 (self.0 as f64) / (AU_PER_PX as f64)
} }
#[inline] #[inline]
pub fn to_snapped(self) -> Au { pub fn to_snapped(self) -> Au {
let res = self.0 % 60i32; let res = self.0 % AU_PER_PX;
return if res >= 30i32 { return Au(self.0 - res + 60i32) } return if res >= 30i32 { return Au(self.0 - res + AU_PER_PX) }
else { return Au(self.0 - res) }; else { return Au(self.0 - res) };
} }
#[inline] #[inline]
pub fn from_f32_px(px: f32) -> Au { pub fn from_f32_px(px: f32) -> Au {
Au((px * 60f32) as i32) Au((px * (AU_PER_PX as f32)) as i32)
} }
#[inline] #[inline]
@ -262,7 +265,7 @@ impl Au {
#[inline] #[inline]
pub fn from_f64_px(px: f64) -> Au { pub fn from_f64_px(px: f64) -> Au {
Au((px * 60.) as i32) Au((px * (AU_PER_PX as f64)) as i32)
} }
} }