mirror of
https://github.com/servo/servo.git
synced 2025-08-06 14:10:11 +01:00
Implement simple layout for text, button, radio, and checkbox inputs. Implement simple interaction for checkboxes and radio buttons.
This commit is contained in:
parent
9b20d6e7d2
commit
f70bb68503
10 changed files with 422 additions and 50 deletions
|
@ -1516,7 +1516,7 @@ impl Flow for BlockFlow {
|
|||
/// any fragments it is responsible for flowing.
|
||||
///
|
||||
/// TODO(pcwalton): Inline blocks.
|
||||
fn bubble_inline_sizes(&mut self, _: &LayoutContext) {
|
||||
fn bubble_inline_sizes(&mut self, layout_context: &LayoutContext) {
|
||||
let _scope = layout_debug_scope!("block::bubble_inline_sizes {:s}", self.base.debug_id());
|
||||
|
||||
let mut flags = self.base.flags;
|
||||
|
@ -1572,7 +1572,7 @@ impl Flow for BlockFlow {
|
|||
max(intrinsic_inline_sizes.preferred_inline_size,
|
||||
left_float_width + right_float_width);
|
||||
|
||||
let fragment_intrinsic_inline_sizes = self.fragment.intrinsic_inline_sizes();
|
||||
let fragment_intrinsic_inline_sizes = self.fragment.intrinsic_inline_sizes(layout_context);
|
||||
intrinsic_inline_sizes.minimum_inline_size = max(intrinsic_inline_sizes.minimum_inline_size,
|
||||
fragment_intrinsic_inline_sizes.minimum_inline_size);
|
||||
intrinsic_inline_sizes.preferred_inline_size = max(intrinsic_inline_sizes.preferred_inline_size,
|
||||
|
|
|
@ -27,12 +27,12 @@ use flow::{Flow, ImmutableFlowUtils, MutableOwnedFlowUtils};
|
|||
use flow::{Descendants, AbsDescendants};
|
||||
use flow;
|
||||
use flow_ref::FlowRef;
|
||||
use fragment::{InlineBlockFragment, InlineBlockFragmentInfo};
|
||||
use fragment::{InlineBlockFragment, InlineBlockFragmentInfo, InputFragment};
|
||||
use fragment::{Fragment, GenericFragment, IframeFragment, IframeFragmentInfo};
|
||||
use fragment::{ImageFragment, ImageFragmentInfo, SpecificFragmentInfo, TableFragment};
|
||||
use fragment::{TableCellFragment, TableColumnFragment, TableColumnFragmentInfo};
|
||||
use fragment::{TableRowFragment, TableWrapperFragment, UnscannedTextFragment};
|
||||
use fragment::{UnscannedTextFragmentInfo};
|
||||
use fragment::{TableRowFragment, TableWrapperFragment, UnscannedTextFragment, InputRadioButton};
|
||||
use fragment::{UnscannedTextFragmentInfo, InputCheckbox, InputButton, InputText, InputFile};
|
||||
use inline::{InlineFragments, InlineFlow};
|
||||
use parallel;
|
||||
use table_wrapper::TableWrapperFlow;
|
||||
|
@ -49,7 +49,7 @@ use wrapper::{Before, After, Normal};
|
|||
|
||||
use gfx::display_list::OpaqueNode;
|
||||
use script::dom::element::{HTMLIFrameElementTypeId, HTMLImageElementTypeId};
|
||||
use script::dom::element::{HTMLObjectElementTypeId};
|
||||
use script::dom::element::{HTMLObjectElementTypeId, HTMLInputElementTypeId};
|
||||
use script::dom::element::{HTMLTableColElementTypeId, HTMLTableDataCellElementTypeId};
|
||||
use script::dom::element::{HTMLTableElementTypeId, HTMLTableHeaderCellElementTypeId};
|
||||
use script::dom::element::{HTMLTableRowElementTypeId, HTMLTableSectionElementTypeId};
|
||||
|
@ -221,6 +221,21 @@ impl<'a> FlowConstructor<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
fn build_fragment_info_for_input(&mut self, node: &ThreadSafeLayoutNode) -> SpecificFragmentInfo {
|
||||
//FIXME: would it make more sense to use HTMLInputElement::input_type instead of the raw
|
||||
// value? definitely for string comparisons.
|
||||
let elem = node.as_element();
|
||||
let data = match elem.get_attr(&ns!(""), "type") {
|
||||
Some("checkbox") => InputCheckbox(node.get_input_checked()),
|
||||
Some("button") | Some("submit") | Some("reset") =>
|
||||
InputButton(node.get_input_value().len() as u32),
|
||||
Some("radio") => InputRadioButton(node.get_input_checked()),
|
||||
Some("file") => InputFile(node.get_input_size()),
|
||||
_ => InputText(node.get_input_size()),
|
||||
};
|
||||
InputFragment(data)
|
||||
}
|
||||
|
||||
/// Builds specific `Fragment` info for the given node.
|
||||
///
|
||||
/// This does *not* construct the text for generated content (but, for generated content with
|
||||
|
@ -230,11 +245,14 @@ impl<'a> FlowConstructor<'a> {
|
|||
pub fn build_specific_fragment_info_for_node(&mut self, node: &ThreadSafeLayoutNode)
|
||||
-> SpecificFragmentInfo {
|
||||
match node.type_id() {
|
||||
Some(ElementNodeTypeId(HTMLIFrameElementTypeId)) => {
|
||||
IframeFragment(IframeFragmentInfo::new(node))
|
||||
}
|
||||
Some(ElementNodeTypeId(HTMLImageElementTypeId)) => {
|
||||
self.build_fragment_info_for_image(node, node.image_url())
|
||||
}
|
||||
Some(ElementNodeTypeId(HTMLIFrameElementTypeId)) => {
|
||||
IframeFragment(IframeFragmentInfo::new(node))
|
||||
Some(ElementNodeTypeId(HTMLInputElementTypeId)) => {
|
||||
self.build_fragment_info_for_input(node)
|
||||
}
|
||||
Some(ElementNodeTypeId(HTMLObjectElementTypeId)) => {
|
||||
let data = node.get_object_data();
|
||||
|
@ -445,7 +463,8 @@ impl<'a> FlowConstructor<'a> {
|
|||
|
||||
// Special case: If this is generated content, then we need to initialize the accumulator
|
||||
// with the fragment corresponding to that content.
|
||||
if node.get_pseudo_element_type() != Normal {
|
||||
if node.get_pseudo_element_type() != Normal ||
|
||||
node.type_id() == Some(ElementNodeTypeId(HTMLInputElementTypeId)) {
|
||||
let fragment_info = UnscannedTextFragment(UnscannedTextFragmentInfo::new(node));
|
||||
let mut fragment = Fragment::new_from_specific_info(node, fragment_info);
|
||||
inline_fragment_accumulator.fragments.push(&mut fragment);
|
||||
|
|
|
@ -128,10 +128,11 @@ impl<E, S: Encoder<E>> Encodable<S, E> for Fragment {
|
|||
/// Info specific to the kind of fragment. Keep this enum small.
|
||||
#[deriving(Clone)]
|
||||
pub enum SpecificFragmentInfo {
|
||||
InlineBlockFragment(InlineBlockFragmentInfo),
|
||||
GenericFragment,
|
||||
ImageFragment(ImageFragmentInfo),
|
||||
IframeFragment(IframeFragmentInfo),
|
||||
ImageFragment(ImageFragmentInfo),
|
||||
InlineBlockFragment(InlineBlockFragmentInfo),
|
||||
InputFragment(InputFragmentInfo),
|
||||
ScannedTextFragment(ScannedTextFragmentInfo),
|
||||
TableFragment,
|
||||
TableCellFragment,
|
||||
|
@ -155,6 +156,38 @@ impl InlineBlockFragmentInfo {
|
|||
}
|
||||
}
|
||||
|
||||
/// A fragment that represents a displayable form element
|
||||
#[deriving(Clone)]
|
||||
pub enum InputFragmentInfo {
|
||||
InputButton(u32),
|
||||
InputText(u32),
|
||||
InputCheckbox(bool),
|
||||
InputRadioButton(bool),
|
||||
InputFile(u32),
|
||||
}
|
||||
|
||||
impl InputFragmentInfo {
|
||||
fn size(&self) -> Option<u32> {
|
||||
match self {
|
||||
&InputText(size) | &InputFile(size) | &InputButton(size) => Some(size),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the original inline-size of the input.
|
||||
fn input_inline_size(&self, font_style: &FontStyle, layout_context: &LayoutContext) -> Au {
|
||||
match self.size() {
|
||||
Some(size) => {
|
||||
let metrics = text::font_metrics_for_style(layout_context.font_context(), font_style);
|
||||
|
||||
// https://html.spec.whatwg.org/#converting-a-character-width-to-pixels
|
||||
metrics.average_advance * (size as i32 - 1) + metrics.max_advance
|
||||
}
|
||||
None => Au::from_px(10)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A fragment that represents a replaced content image and its accompanying borders, shadows, etc.
|
||||
#[deriving(Clone)]
|
||||
pub struct ImageFragmentInfo {
|
||||
|
@ -499,7 +532,8 @@ impl Fragment {
|
|||
/// replaced elements.
|
||||
fn style_specified_intrinsic_inline_size(&self) -> IntrinsicISizes {
|
||||
let (use_margins, use_padding) = match self.specific {
|
||||
GenericFragment | IframeFragment(_) | ImageFragment(_) | InlineBlockFragment(_) => (true, true),
|
||||
GenericFragment | IframeFragment(_) | ImageFragment(_) | InlineBlockFragment(_) |
|
||||
InputFragment(_) => (true, true),
|
||||
TableFragment | TableCellFragment => (false, true),
|
||||
TableWrapperFragment => (true, false),
|
||||
TableRowFragment => (false, false),
|
||||
|
@ -1129,7 +1163,7 @@ impl Fragment {
|
|||
text_fragment))
|
||||
}
|
||||
GenericFragment | IframeFragment(..) | TableFragment | TableCellFragment |
|
||||
TableRowFragment | TableWrapperFragment | InlineBlockFragment(_) => {
|
||||
TableRowFragment | TableWrapperFragment | InlineBlockFragment(_) | InputFragment(_) => {
|
||||
// FIXME(pcwalton): This is a bit of an abuse of the logging infrastructure. We
|
||||
// should have a real `SERVO_DEBUG` system.
|
||||
debug!("{:?}", self.build_debug_borders_around_fragment(display_list, flow_origin))
|
||||
|
@ -1193,7 +1227,7 @@ impl Fragment {
|
|||
}
|
||||
|
||||
/// Returns the intrinsic inline-sizes of this fragment.
|
||||
pub fn intrinsic_inline_sizes(&mut self) -> IntrinsicISizes {
|
||||
pub fn intrinsic_inline_sizes(&mut self, layout_context: &LayoutContext) -> IntrinsicISizes {
|
||||
let mut result = self.style_specified_intrinsic_inline_size();
|
||||
|
||||
match self.specific {
|
||||
|
@ -1214,6 +1248,12 @@ impl Fragment {
|
|||
result.preferred_inline_size = max(result.preferred_inline_size,
|
||||
image_inline_size);
|
||||
}
|
||||
InputFragment(ref input_fragment_info) => {
|
||||
let font_style = text::computed_style_to_font_style(&*self.style);
|
||||
let input_inline_size = input_fragment_info.input_inline_size(&font_style, layout_context);
|
||||
result.minimum_inline_size = input_inline_size;
|
||||
result.preferred_inline_size = input_inline_size;
|
||||
}
|
||||
ScannedTextFragment(ref text_fragment_info) => {
|
||||
let range = &text_fragment_info.range;
|
||||
let min_line_inline_size = text_fragment_info.run.min_width_for_range(range);
|
||||
|
@ -1260,7 +1300,7 @@ impl Fragment {
|
|||
pub fn content_inline_size(&self) -> Au {
|
||||
match self.specific {
|
||||
GenericFragment | IframeFragment(_) | TableFragment | TableCellFragment | TableRowFragment |
|
||||
TableWrapperFragment | InlineBlockFragment(_) => Au(0),
|
||||
TableWrapperFragment | InlineBlockFragment(_) | InputFragment(_) => Au(0),
|
||||
ImageFragment(ref image_fragment_info) => {
|
||||
image_fragment_info.computed_inline_size()
|
||||
}
|
||||
|
@ -1278,7 +1318,8 @@ impl Fragment {
|
|||
pub fn content_block_size(&self, layout_context: &LayoutContext) -> Au {
|
||||
match self.specific {
|
||||
GenericFragment | IframeFragment(_) | TableFragment | TableCellFragment |
|
||||
TableRowFragment | TableWrapperFragment | InlineBlockFragment(_) => Au(0),
|
||||
TableRowFragment | TableWrapperFragment | InlineBlockFragment(_) |
|
||||
InputFragment(_) => Au(0),
|
||||
ImageFragment(ref image_fragment_info) => {
|
||||
image_fragment_info.computed_block_size()
|
||||
}
|
||||
|
@ -1312,7 +1353,7 @@ impl Fragment {
|
|||
-> Option<(SplitInfo, Option<SplitInfo>, Arc<Box<TextRun>> /* TODO(bjz): remove */)> {
|
||||
match self.specific {
|
||||
GenericFragment | IframeFragment(_) | ImageFragment(_) | TableFragment | TableCellFragment |
|
||||
TableRowFragment | TableWrapperFragment => None,
|
||||
TableRowFragment | TableWrapperFragment | InputFragment(_) => None,
|
||||
TableColumnFragment(_) => fail!("Table column fragments do not need to split"),
|
||||
UnscannedTextFragment(_) => fail!("Unscanned text fragments should have been scanned by now!"),
|
||||
InlineBlockFragment(_) => fail!("Inline blocks do not get split"),
|
||||
|
@ -1353,7 +1394,7 @@ impl Fragment {
|
|||
-> Option<(Option<SplitInfo>, Option<SplitInfo>, Arc<Box<TextRun>> /* TODO(bjz): remove */)> {
|
||||
match self.specific {
|
||||
GenericFragment | IframeFragment(_) | ImageFragment(_) | TableFragment | TableCellFragment |
|
||||
TableRowFragment | TableWrapperFragment | InlineBlockFragment(_) => None,
|
||||
TableRowFragment | TableWrapperFragment | InlineBlockFragment(_) | InputFragment(_) => None,
|
||||
TableColumnFragment(_) => fail!("Table column fragments do not have inline_size"),
|
||||
UnscannedTextFragment(_) => fail!("Unscanned text fragments should have been scanned by now!"),
|
||||
ScannedTextFragment(ref text_fragment_info) => {
|
||||
|
@ -1457,7 +1498,7 @@ impl Fragment {
|
|||
container_inline_size: Au) {
|
||||
match self.specific {
|
||||
GenericFragment | IframeFragment(_) | TableFragment | TableCellFragment |
|
||||
TableRowFragment | TableWrapperFragment => return,
|
||||
TableRowFragment | TableWrapperFragment | InputFragment(_) => return,
|
||||
TableColumnFragment(_) => fail!("Table column fragments do not have inline_size"),
|
||||
UnscannedTextFragment(_) => {
|
||||
fail!("Unscanned text fragments should have been scanned by now!")
|
||||
|
@ -1540,7 +1581,7 @@ impl Fragment {
|
|||
pub fn assign_replaced_block_size_if_necessary(&mut self) {
|
||||
match self.specific {
|
||||
GenericFragment | IframeFragment(_) | TableFragment | TableCellFragment |
|
||||
TableRowFragment | TableWrapperFragment => return,
|
||||
TableRowFragment | TableWrapperFragment | InputFragment(_) => return,
|
||||
TableColumnFragment(_) => fail!("Table column fragments do not have block_size"),
|
||||
UnscannedTextFragment(_) => {
|
||||
fail!("Unscanned text fragments should have been scanned by now!")
|
||||
|
@ -1682,7 +1723,7 @@ impl Fragment {
|
|||
InlineBlockFragment(_) | TableWrapperFragment => false,
|
||||
GenericFragment | IframeFragment(_) | ImageFragment(_) | ScannedTextFragment(_) |
|
||||
TableFragment | TableCellFragment | TableColumnFragment(_) | TableRowFragment |
|
||||
UnscannedTextFragment(_) => true,
|
||||
UnscannedTextFragment(_) | InputFragment(_) => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1703,6 +1744,7 @@ impl fmt::Show for Fragment {
|
|||
TableWrapperFragment => "TableWrapperFragment",
|
||||
UnscannedTextFragment(_) => "UnscannedTextFragment",
|
||||
InlineBlockFragment(_) => "InlineBlockFragment",
|
||||
InputFragment(_) => "InputFragment",
|
||||
}));
|
||||
try!(write!(f, "bp {}", self.border_padding));
|
||||
try!(write!(f, " "));
|
||||
|
|
|
@ -955,7 +955,7 @@ impl Flow for InlineFlow {
|
|||
self
|
||||
}
|
||||
|
||||
fn bubble_inline_sizes(&mut self, _: &LayoutContext) {
|
||||
fn bubble_inline_sizes(&mut self, layout_context: &LayoutContext) {
|
||||
let _scope = layout_debug_scope!("inline::bubble_inline_sizes {:s}", self.base.debug_id());
|
||||
|
||||
let writing_mode = self.base.writing_mode;
|
||||
|
@ -968,7 +968,7 @@ impl Flow for InlineFlow {
|
|||
debug!("Flow: measuring {}", *fragment);
|
||||
|
||||
let fragment_intrinsic_inline_sizes =
|
||||
fragment.intrinsic_inline_sizes();
|
||||
fragment.intrinsic_inline_sizes(layout_context);
|
||||
intrinsic_inline_sizes.minimum_inline_size = max(
|
||||
intrinsic_inline_sizes.minimum_inline_size,
|
||||
fragment_intrinsic_inline_sizes.minimum_inline_size);
|
||||
|
|
|
@ -171,7 +171,7 @@ impl Flow for TableFlow {
|
|||
/// table layout calculation.
|
||||
/// The maximum min/pref inline-sizes of each column are set from the rows for the automatic
|
||||
/// table layout calculation.
|
||||
fn bubble_inline_sizes(&mut self, _: &LayoutContext) {
|
||||
fn bubble_inline_sizes(&mut self, layout_context: &LayoutContext) {
|
||||
let _scope = layout_debug_scope!("table::bubble_inline_sizes {:s}",
|
||||
self.block_flow.base.debug_id());
|
||||
|
||||
|
@ -239,7 +239,8 @@ impl Flow for TableFlow {
|
|||
}
|
||||
}
|
||||
|
||||
let fragment_intrinsic_inline_sizes = self.block_flow.fragment.intrinsic_inline_sizes();
|
||||
let fragment_intrinsic_inline_sizes =
|
||||
self.block_flow.fragment.intrinsic_inline_sizes(layout_context);
|
||||
self.block_flow.base.intrinsic_inline_sizes.minimum_inline_size = min_inline_size;
|
||||
self.block_flow.base.intrinsic_inline_sizes.preferred_inline_size =
|
||||
max(min_inline_size, pref_inline_size);
|
||||
|
|
|
@ -36,13 +36,14 @@
|
|||
use css::node_style::StyledNode;
|
||||
use util::{LayoutDataAccess, LayoutDataWrapper, PrivateLayoutData};
|
||||
|
||||
use script::dom::bindings::codegen::InheritTypes::{HTMLIFrameElementDerived};
|
||||
use script::dom::bindings::codegen::InheritTypes::{HTMLIFrameElementDerived, HTMLInputElementDerived};
|
||||
use script::dom::bindings::codegen::InheritTypes::{HTMLImageElementDerived, TextDerived};
|
||||
use script::dom::bindings::js::JS;
|
||||
use script::dom::element::{Element, HTMLAreaElementTypeId, HTMLAnchorElementTypeId};
|
||||
use script::dom::element::{HTMLLinkElementTypeId, LayoutElementHelpers, RawLayoutElementHelpers};
|
||||
use script::dom::htmliframeelement::HTMLIFrameElement;
|
||||
use script::dom::htmlimageelement::{HTMLImageElement, LayoutHTMLImageElementHelpers};
|
||||
use script::dom::htmlinputelement::{HTMLInputElement, LayoutHTMLInputElementHelpers};
|
||||
use script::dom::node::{DocumentNodeTypeId, ElementNodeTypeId, Node, NodeTypeId};
|
||||
use script::dom::node::{LayoutNodeHelpers, RawLayoutNodeHelpers, SharedLayoutData, TextNodeTypeId};
|
||||
use script::dom::text::Text;
|
||||
|
@ -184,11 +185,15 @@ impl<'ln> TLayoutNode for LayoutNode<'ln> {
|
|||
|
||||
fn text(&self) -> String {
|
||||
unsafe {
|
||||
if !self.get().is_text() {
|
||||
if self.get().is_text() {
|
||||
let text: JS<Text> = self.get_jsmanaged().transmute_copy();
|
||||
(*text.unsafe_get()).characterdata.data.deref().borrow().clone()
|
||||
} else if self.get().is_htmlinputelement() {
|
||||
let input: JS<HTMLInputElement> = self.get_jsmanaged().transmute_copy();
|
||||
input.get_value_for_layout()
|
||||
} else {
|
||||
fail!("not text!")
|
||||
}
|
||||
let text: JS<Text> = self.get_jsmanaged().transmute_copy();
|
||||
(*text.unsafe_get()).characterdata.data.deref().borrow().clone()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -567,14 +572,7 @@ impl<'ln> TLayoutNode for ThreadSafeLayoutNode<'ln> {
|
|||
return get_content(&after_style.get_box().content)
|
||||
}
|
||||
}
|
||||
|
||||
unsafe {
|
||||
if !self.get().is_text() {
|
||||
fail!("not text!")
|
||||
}
|
||||
let text: JS<Text> = self.get_jsmanaged().transmute_copy();
|
||||
(*text.unsafe_get()).characterdata.data.deref().borrow().clone()
|
||||
}
|
||||
self.node.text()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -732,6 +730,36 @@ impl<'ln> ThreadSafeLayoutNode<'ln> {
|
|||
_ => false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_input_checked(&self) -> bool {
|
||||
unsafe {
|
||||
if !self.get().is_htmlinputelement() {
|
||||
fail!("not an input element!")
|
||||
}
|
||||
let input: JS<HTMLInputElement> = self.get_jsmanaged().transmute_copy();
|
||||
input.get_checked_for_layout()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_input_value(&self) -> String {
|
||||
unsafe {
|
||||
if !self.get().is_htmlinputelement() {
|
||||
fail!("not an input element!")
|
||||
}
|
||||
let input: JS<HTMLInputElement> = self.get_jsmanaged().transmute_copy();
|
||||
input.get_value_for_layout()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_input_size(&self) -> u32 {
|
||||
unsafe {
|
||||
if !self.get().is_htmlinputelement() {
|
||||
fail!("not an input element!")
|
||||
}
|
||||
let input: JS<HTMLInputElement> = self.get_jsmanaged().transmute_copy();
|
||||
input.get_size_for_layout()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ThreadSafeLayoutNodeChildrenIterator<'a> {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue