diff --git a/.editorconfig b/.editorconfig index a1afa2b5a..17e0e79ad 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,44 +1,398 @@ [*.cs] -indent_style = tab -indent_size = 8 -tab_width = 8 -csharp_new_line_before_open_brace = methods,local_functions -csharp_new_line_before_else = false -csharp_new_line_before_catch = false -csharp_new_line_before_finally = false -end_of_line = crlf - csharp_indent_case_contents = true -csharp_indent_switch_labels = false csharp_indent_labels = flush_left csharp_space_after_keywords_in_control_flow_statements = true csharp_space_between_method_declaration_parameter_list_parentheses = false csharp_space_between_method_call_parameter_list_parentheses = false csharp_preserve_single_line_blocks = true -dotnet_style_require_accessibility_modifiers = never -csharp_style_var_when_type_is_apparent = true:none -csharp_prefer_braces = true:none -csharp_space_before_open_square_brackets = true +dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion +csharp_prefer_braces = true:warning csharp_space_between_method_call_name_and_opening_parenthesis = true csharp_space_between_method_declaration_name_and_open_parenthesis = true # Microsoft .NET properties -csharp_style_var_elsewhere = true:none +csharp_indent_braces = false +csharp_indent_switch_labels = true +csharp_new_line_before_catch = true +csharp_new_line_before_else = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_object_initializers = false +csharp_new_line_before_open_brace = all +csharp_new_line_between_query_expression_clauses = true +csharp_preferred_modifier_order = public, private, protected, internal, file, new, static, abstract, virtual, sealed, readonly, override, extern, unsafe, volatile, async, required:warning +csharp_space_after_cast = false +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_comma = true +csharp_space_after_dot = false +csharp_space_after_semicolon_in_for_statement = true +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_before_comma = false +csharp_space_before_dot = false +csharp_space_before_semicolon_in_for_statement = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_between_square_brackets = false +dotnet_diagnostic.bc40000.severity = warning +dotnet_diagnostic.bc400005.severity = warning +dotnet_diagnostic.bc40008.severity = warning +dotnet_diagnostic.bc40056.severity = warning +dotnet_diagnostic.bc42016.severity = warning +dotnet_diagnostic.bc42024.severity = warning +dotnet_diagnostic.bc42025.severity = warning +dotnet_diagnostic.bc42104.severity = warning +dotnet_diagnostic.bc42105.severity = warning +dotnet_diagnostic.bc42106.severity = warning +dotnet_diagnostic.bc42107.severity = warning +dotnet_diagnostic.bc42304.severity = warning +dotnet_diagnostic.bc42309.severity = warning +dotnet_diagnostic.bc42322.severity = warning +dotnet_diagnostic.bc42349.severity = warning +dotnet_diagnostic.bc42353.severity = warning +dotnet_diagnostic.bc42354.severity = warning +dotnet_diagnostic.bc42355.severity = warning +dotnet_diagnostic.bc42356.severity = warning +dotnet_diagnostic.bc42358.severity = warning +dotnet_diagnostic.bc42504.severity = warning +dotnet_diagnostic.bc42505.severity = warning +dotnet_diagnostic.ca2252.severity = error +dotnet_diagnostic.cs0067.severity = warning +dotnet_diagnostic.cs0078.severity = warning +dotnet_diagnostic.cs0108.severity = warning +dotnet_diagnostic.cs0109.severity = warning +dotnet_diagnostic.cs0114.severity = warning +dotnet_diagnostic.cs0162.severity = warning +dotnet_diagnostic.cs0164.severity = warning +dotnet_diagnostic.cs0168.severity = warning +dotnet_diagnostic.cs0169.severity = warning +dotnet_diagnostic.cs0183.severity = warning +dotnet_diagnostic.cs0184.severity = warning +dotnet_diagnostic.cs0197.severity = warning +dotnet_diagnostic.cs0219.severity = warning +dotnet_diagnostic.cs0252.severity = warning +dotnet_diagnostic.cs0253.severity = warning +dotnet_diagnostic.cs0282.severity = warning +dotnet_diagnostic.cs0414.severity = warning +dotnet_diagnostic.cs0420.severity = warning +dotnet_diagnostic.cs0458.severity = warning +dotnet_diagnostic.cs0464.severity = warning +dotnet_diagnostic.cs0465.severity = warning +dotnet_diagnostic.cs0469.severity = warning +dotnet_diagnostic.cs0472.severity = warning +dotnet_diagnostic.cs0612.severity = warning +dotnet_diagnostic.cs0618.severity = warning +dotnet_diagnostic.cs0628.severity = warning +dotnet_diagnostic.cs0642.severity = warning +dotnet_diagnostic.cs0649.severity = warning +dotnet_diagnostic.cs0652.severity = warning +dotnet_diagnostic.cs0657.severity = warning +dotnet_diagnostic.cs0658.severity = warning +dotnet_diagnostic.cs0659.severity = warning +dotnet_diagnostic.cs0660.severity = warning +dotnet_diagnostic.cs0661.severity = warning +dotnet_diagnostic.cs0665.severity = warning +dotnet_diagnostic.cs0672.severity = warning +dotnet_diagnostic.cs0675.severity = warning +dotnet_diagnostic.cs0693.severity = warning +dotnet_diagnostic.cs0728.severity = warning +dotnet_diagnostic.cs1030.severity = warning +dotnet_diagnostic.cs1058.severity = warning +dotnet_diagnostic.cs1066.severity = warning +dotnet_diagnostic.cs1522.severity = warning +dotnet_diagnostic.cs1570.severity = warning +dotnet_diagnostic.cs1571.severity = warning +dotnet_diagnostic.cs1572.severity = warning +dotnet_diagnostic.cs1573.severity = warning +dotnet_diagnostic.cs1574.severity = warning +dotnet_diagnostic.cs1580.severity = warning +dotnet_diagnostic.cs1581.severity = warning +dotnet_diagnostic.cs1584.severity = warning +dotnet_diagnostic.cs1587.severity = warning +dotnet_diagnostic.cs1589.severity = warning +dotnet_diagnostic.cs1590.severity = warning +dotnet_diagnostic.cs1591.severity = warning +dotnet_diagnostic.cs1592.severity = warning +dotnet_diagnostic.cs1710.severity = warning +dotnet_diagnostic.cs1711.severity = warning +dotnet_diagnostic.cs1712.severity = warning +dotnet_diagnostic.cs1717.severity = warning +dotnet_diagnostic.cs1723.severity = warning +dotnet_diagnostic.cs1911.severity = warning +dotnet_diagnostic.cs1957.severity = warning +dotnet_diagnostic.cs1981.severity = warning +dotnet_diagnostic.cs1998.severity = warning +dotnet_diagnostic.cs4014.severity = warning +dotnet_diagnostic.cs7022.severity = warning +dotnet_diagnostic.cs7023.severity = warning +dotnet_diagnostic.cs7095.severity = warning +dotnet_diagnostic.cs8073.severity = warning +dotnet_diagnostic.cs8094.severity = warning +dotnet_diagnostic.cs8123.severity = warning +dotnet_diagnostic.cs8321.severity = warning +dotnet_diagnostic.cs8383.severity = warning +dotnet_diagnostic.cs8424.severity = warning +dotnet_diagnostic.cs8425.severity = warning +dotnet_diagnostic.cs8500.severity = warning +dotnet_diagnostic.cs8509.severity = warning +dotnet_diagnostic.cs8519.severity = warning +dotnet_diagnostic.cs8520.severity = warning +dotnet_diagnostic.cs8524.severity = warning +dotnet_diagnostic.cs8597.severity = warning +dotnet_diagnostic.cs8600.severity = warning +dotnet_diagnostic.cs8601.severity = warning +dotnet_diagnostic.cs8602.severity = warning +dotnet_diagnostic.cs8603.severity = warning +dotnet_diagnostic.cs8604.severity = warning +dotnet_diagnostic.cs8605.severity = warning +dotnet_diagnostic.cs8607.severity = warning +dotnet_diagnostic.cs8608.severity = warning +dotnet_diagnostic.cs8609.severity = warning +dotnet_diagnostic.cs8610.severity = warning +dotnet_diagnostic.cs8611.severity = warning +dotnet_diagnostic.cs8612.severity = warning +dotnet_diagnostic.cs8613.severity = warning +dotnet_diagnostic.cs8614.severity = warning +dotnet_diagnostic.cs8615.severity = warning +dotnet_diagnostic.cs8616.severity = warning +dotnet_diagnostic.cs8617.severity = warning +dotnet_diagnostic.cs8618.severity = warning +dotnet_diagnostic.cs8619.severity = warning +dotnet_diagnostic.cs8620.severity = warning +dotnet_diagnostic.cs8621.severity = warning +dotnet_diagnostic.cs8622.severity = warning +dotnet_diagnostic.cs8624.severity = warning +dotnet_diagnostic.cs8625.severity = warning +dotnet_diagnostic.cs8629.severity = warning +dotnet_diagnostic.cs8631.severity = warning +dotnet_diagnostic.cs8632.severity = warning +dotnet_diagnostic.cs8633.severity = warning +dotnet_diagnostic.cs8634.severity = warning +dotnet_diagnostic.cs8643.severity = warning +dotnet_diagnostic.cs8644.severity = warning +dotnet_diagnostic.cs8645.severity = warning +dotnet_diagnostic.cs8655.severity = warning +dotnet_diagnostic.cs8656.severity = warning +dotnet_diagnostic.cs8667.severity = warning +dotnet_diagnostic.cs8669.severity = warning +dotnet_diagnostic.cs8670.severity = warning +dotnet_diagnostic.cs8714.severity = warning +dotnet_diagnostic.cs8762.severity = warning +dotnet_diagnostic.cs8763.severity = warning +dotnet_diagnostic.cs8764.severity = warning +dotnet_diagnostic.cs8765.severity = warning +dotnet_diagnostic.cs8766.severity = warning +dotnet_diagnostic.cs8767.severity = warning +dotnet_diagnostic.cs8768.severity = warning +dotnet_diagnostic.cs8769.severity = warning +dotnet_diagnostic.cs8770.severity = warning +dotnet_diagnostic.cs8774.severity = warning +dotnet_diagnostic.cs8775.severity = warning +dotnet_diagnostic.cs8776.severity = warning +dotnet_diagnostic.cs8777.severity = warning +dotnet_diagnostic.cs8794.severity = warning +dotnet_diagnostic.cs8819.severity = warning +dotnet_diagnostic.cs8824.severity = warning +dotnet_diagnostic.cs8825.severity = warning +dotnet_diagnostic.cs8846.severity = warning +dotnet_diagnostic.cs8847.severity = warning +dotnet_diagnostic.cs8851.severity = warning +dotnet_diagnostic.cs8860.severity = warning +dotnet_diagnostic.cs8892.severity = warning +dotnet_diagnostic.cs8907.severity = warning +dotnet_diagnostic.cs8947.severity = warning +dotnet_diagnostic.cs8960.severity = warning +dotnet_diagnostic.cs8961.severity = warning +dotnet_diagnostic.cs8962.severity = warning +dotnet_diagnostic.cs8963.severity = warning +dotnet_diagnostic.cs8965.severity = warning +dotnet_diagnostic.cs8966.severity = warning +dotnet_diagnostic.cs8971.severity = warning +dotnet_diagnostic.cs8974.severity = warning +dotnet_diagnostic.cs8981.severity = warning +dotnet_diagnostic.cs9042.severity = warning +dotnet_diagnostic.cs9073.severity = warning +dotnet_diagnostic.cs9074.severity = warning +dotnet_diagnostic.cs9080.severity = warning +dotnet_diagnostic.cs9081.severity = warning +dotnet_diagnostic.cs9082.severity = warning +dotnet_diagnostic.cs9083.severity = warning +dotnet_diagnostic.cs9084.severity = warning +dotnet_diagnostic.cs9085.severity = warning +dotnet_diagnostic.cs9086.severity = warning +dotnet_diagnostic.cs9087.severity = warning +dotnet_diagnostic.cs9088.severity = warning +dotnet_diagnostic.cs9089.severity = warning +dotnet_diagnostic.cs9090.severity = warning +dotnet_diagnostic.cs9091.severity = warning +dotnet_diagnostic.cs9092.severity = warning +dotnet_diagnostic.cs9093.severity = warning +dotnet_diagnostic.cs9094.severity = warning +dotnet_diagnostic.cs9095.severity = warning +dotnet_diagnostic.cs9097.severity = warning +dotnet_diagnostic.cs9099.severity = warning +dotnet_diagnostic.cs9100.severity = warning +dotnet_diagnostic.cs9107.severity = warning +dotnet_diagnostic.cs9113.severity = warning +dotnet_diagnostic.cs9124.severity = warning +dotnet_diagnostic.cs9154.severity = warning +dotnet_diagnostic.cs9158.severity = warning +dotnet_diagnostic.cs9159.severity = warning +dotnet_diagnostic.cs9179.severity = warning +dotnet_diagnostic.cs9181.severity = warning +dotnet_diagnostic.cs9182.severity = warning +dotnet_diagnostic.cs9183.severity = warning +dotnet_diagnostic.cs9184.severity = warning +dotnet_diagnostic.cs9191.severity = warning +dotnet_diagnostic.cs9192.severity = warning +dotnet_diagnostic.cs9193.severity = warning +dotnet_diagnostic.cs9195.severity = warning +dotnet_diagnostic.cs9196.severity = warning +dotnet_diagnostic.cs9200.severity = warning +dotnet_diagnostic.cs9208.severity = warning +dotnet_diagnostic.cs9209.severity = warning +dotnet_diagnostic.wme006.severity = warning +dotnet_naming_rule.constants_rule.import_to_resharper = as_predefined +dotnet_naming_rule.constants_rule.severity = warning +dotnet_naming_rule.constants_rule.style = upper_camel_case_style +dotnet_naming_rule.constants_rule.symbols = constants_symbols +dotnet_naming_rule.event_rule.import_to_resharper = as_predefined +dotnet_naming_rule.event_rule.severity = warning +dotnet_naming_rule.event_rule.style = upper_camel_case_style +dotnet_naming_rule.event_rule.symbols = event_symbols +dotnet_naming_rule.interfaces_rule.import_to_resharper = as_predefined +dotnet_naming_rule.interfaces_rule.severity = warning +dotnet_naming_rule.interfaces_rule.style = i_upper_camel_case_style +dotnet_naming_rule.interfaces_rule.symbols = interfaces_symbols +dotnet_naming_rule.locals_rule.import_to_resharper = as_predefined +dotnet_naming_rule.locals_rule.severity = warning +dotnet_naming_rule.locals_rule.style = lower_camel_case_style_1 +dotnet_naming_rule.locals_rule.symbols = locals_symbols +dotnet_naming_rule.local_constants_rule.import_to_resharper = as_predefined +dotnet_naming_rule.local_constants_rule.severity = warning +dotnet_naming_rule.local_constants_rule.style = lower_camel_case_style_1 +dotnet_naming_rule.local_constants_rule.symbols = local_constants_symbols +dotnet_naming_rule.local_functions_rule.import_to_resharper = as_predefined +dotnet_naming_rule.local_functions_rule.severity = warning +dotnet_naming_rule.local_functions_rule.style = upper_camel_case_style +dotnet_naming_rule.local_functions_rule.symbols = local_functions_symbols +dotnet_naming_rule.method_rule.import_to_resharper = as_predefined +dotnet_naming_rule.method_rule.severity = warning +dotnet_naming_rule.method_rule.style = upper_camel_case_style +dotnet_naming_rule.method_rule.symbols = method_symbols +dotnet_naming_rule.parameters_rule.import_to_resharper = as_predefined +dotnet_naming_rule.parameters_rule.severity = warning +dotnet_naming_rule.parameters_rule.style = lower_camel_case_style_1 +dotnet_naming_rule.parameters_rule.symbols = parameters_symbols +dotnet_naming_rule.private_constants_rule.import_to_resharper = as_predefined +dotnet_naming_rule.private_constants_rule.severity = warning +dotnet_naming_rule.private_constants_rule.style = upper_camel_case_style +dotnet_naming_rule.private_constants_rule.symbols = private_constants_symbols +dotnet_naming_rule.private_instance_fields_rule.import_to_resharper = as_predefined +dotnet_naming_rule.private_instance_fields_rule.severity = warning +dotnet_naming_rule.private_instance_fields_rule.style = lower_camel_case_style +dotnet_naming_rule.private_instance_fields_rule.symbols = private_instance_fields_symbols +dotnet_naming_rule.private_static_fields_rule.import_to_resharper = as_predefined +dotnet_naming_rule.private_static_fields_rule.severity = warning +dotnet_naming_rule.private_static_fields_rule.style = lower_camel_case_style +dotnet_naming_rule.private_static_fields_rule.symbols = private_static_fields_symbols +dotnet_naming_rule.private_static_readonly_rule.import_to_resharper = as_predefined +dotnet_naming_rule.private_static_readonly_rule.severity = warning +dotnet_naming_rule.private_static_readonly_rule.style = upper_camel_case_style +dotnet_naming_rule.private_static_readonly_rule.symbols = private_static_readonly_symbols +dotnet_naming_rule.property_rule.import_to_resharper = as_predefined +dotnet_naming_rule.property_rule.severity = warning +dotnet_naming_rule.property_rule.style = upper_camel_case_style +dotnet_naming_rule.property_rule.symbols = property_symbols +dotnet_naming_rule.public_fields_rule.import_to_resharper = as_predefined +dotnet_naming_rule.public_fields_rule.severity = warning +dotnet_naming_rule.public_fields_rule.style = upper_camel_case_style +dotnet_naming_rule.public_fields_rule.symbols = public_fields_symbols +dotnet_naming_rule.static_readonly_rule.import_to_resharper = as_predefined +dotnet_naming_rule.static_readonly_rule.severity = warning +dotnet_naming_rule.static_readonly_rule.style = upper_camel_case_style +dotnet_naming_rule.static_readonly_rule.symbols = static_readonly_symbols +dotnet_naming_rule.types_and_namespaces_rule.import_to_resharper = as_predefined +dotnet_naming_rule.types_and_namespaces_rule.severity = warning +dotnet_naming_rule.types_and_namespaces_rule.style = upper_camel_case_style +dotnet_naming_rule.types_and_namespaces_rule.symbols = types_and_namespaces_symbols +dotnet_naming_rule.type_parameters_rule.import_to_resharper = as_predefined +dotnet_naming_rule.type_parameters_rule.severity = warning +dotnet_naming_rule.type_parameters_rule.style = t_upper_camel_case_style +dotnet_naming_rule.type_parameters_rule.symbols = type_parameters_symbols +dotnet_naming_style.all_upper_style.capitalization = all_upper +dotnet_naming_style.all_upper_style.word_separator = _ +dotnet_naming_style.i_upper_camel_case_style.capitalization = pascal_case +dotnet_naming_style.i_upper_camel_case_style.required_prefix = I +dotnet_naming_style.lower_camel_case_style.capitalization = camel_case +dotnet_naming_style.lower_camel_case_style.required_prefix = _ +dotnet_naming_style.lower_camel_case_style_1.capitalization = camel_case +dotnet_naming_style.t_upper_camel_case_style.capitalization = pascal_case +dotnet_naming_style.t_upper_camel_case_style.required_prefix = T +dotnet_naming_style.upper_camel_case_style.capitalization = pascal_case +dotnet_naming_symbols.constants_symbols.applicable_accessibilities = public,internal,protected,protected_internal,private_protected +dotnet_naming_symbols.constants_symbols.applicable_kinds = field +dotnet_naming_symbols.constants_symbols.required_modifiers = const +dotnet_naming_symbols.event_symbols.applicable_accessibilities = * +dotnet_naming_symbols.event_symbols.applicable_kinds = event +dotnet_naming_symbols.interfaces_symbols.applicable_accessibilities = * +dotnet_naming_symbols.interfaces_symbols.applicable_kinds = interface +dotnet_naming_symbols.locals_symbols.applicable_accessibilities = * +dotnet_naming_symbols.locals_symbols.applicable_kinds = local +dotnet_naming_symbols.local_constants_symbols.applicable_accessibilities = * +dotnet_naming_symbols.local_constants_symbols.applicable_kinds = local +dotnet_naming_symbols.local_constants_symbols.required_modifiers = const +dotnet_naming_symbols.local_functions_symbols.applicable_accessibilities = * +dotnet_naming_symbols.local_functions_symbols.applicable_kinds = local_function +dotnet_naming_symbols.method_symbols.applicable_accessibilities = * +dotnet_naming_symbols.method_symbols.applicable_kinds = method +dotnet_naming_symbols.parameters_symbols.applicable_accessibilities = * +dotnet_naming_symbols.parameters_symbols.applicable_kinds = parameter +dotnet_naming_symbols.private_constants_symbols.applicable_accessibilities = private +dotnet_naming_symbols.private_constants_symbols.applicable_kinds = field +dotnet_naming_symbols.private_constants_symbols.required_modifiers = const +dotnet_naming_symbols.private_instance_fields_symbols.applicable_accessibilities = private +dotnet_naming_symbols.private_instance_fields_symbols.applicable_kinds = field +dotnet_naming_symbols.private_static_fields_symbols.applicable_accessibilities = private +dotnet_naming_symbols.private_static_fields_symbols.applicable_kinds = field +dotnet_naming_symbols.private_static_fields_symbols.required_modifiers = static +dotnet_naming_symbols.private_static_readonly_symbols.applicable_accessibilities = private +dotnet_naming_symbols.private_static_readonly_symbols.applicable_kinds = field +dotnet_naming_symbols.private_static_readonly_symbols.required_modifiers = static,readonly +dotnet_naming_symbols.property_symbols.applicable_accessibilities = * +dotnet_naming_symbols.property_symbols.applicable_kinds = property +dotnet_naming_symbols.public_fields_symbols.applicable_accessibilities = public,internal,protected,protected_internal,private_protected +dotnet_naming_symbols.public_fields_symbols.applicable_kinds = field +dotnet_naming_symbols.static_readonly_symbols.applicable_accessibilities = public,internal,protected,protected_internal,private_protected +dotnet_naming_symbols.static_readonly_symbols.applicable_kinds = field +dotnet_naming_symbols.static_readonly_symbols.required_modifiers = static,readonly +dotnet_naming_symbols.types_and_namespaces_symbols.applicable_accessibilities = * +dotnet_naming_symbols.types_and_namespaces_symbols.applicable_kinds = namespace,class,struct,enum,delegate +dotnet_naming_symbols.type_parameters_symbols.applicable_accessibilities = * +dotnet_naming_symbols.type_parameters_symbols.applicable_kinds = type_parameter +dotnet_separate_import_directive_groups = false +dotnet_sort_system_directives_first = true +dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary:suggestion +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:suggestion +dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary:suggestion +dotnet_style_predefined_type_for_locals_parameters_members = true:error +dotnet_style_predefined_type_for_member_access = true:error +dotnet_style_qualification_for_event = false:warning +dotnet_style_qualification_for_field = false:warning +dotnet_style_qualification_for_method = false:warning +dotnet_style_qualification_for_property = false:warning +file_header_template = # ReSharper properties csharp_space_around_binary_operators = before_and_after csharp_using_directive_placement = outside_namespace:silent csharp_prefer_simple_using_statement = true:suggestion -csharp_style_namespace_declarations = file_scoped:none +csharp_style_namespace_declarations = file_scoped:error csharp_style_prefer_method_group_conversion = true:silent csharp_style_prefer_top_level_statements = true:silent csharp_style_prefer_primary_constructors = true:suggestion -csharp_style_expression_bodied_methods = true:none -csharp_style_expression_bodied_constructors = true:none csharp_style_expression_bodied_operators = false:silent -csharp_style_expression_bodied_properties = true:silent csharp_style_expression_bodied_indexers = true:silent -csharp_style_expression_bodied_accessors = true:silent csharp_style_expression_bodied_lambdas = true:silent csharp_style_expression_bodied_local_functions = false:silent csharp_style_throw_expression = true:suggestion @@ -54,7 +408,6 @@ csharp_style_inlined_variable_declaration = true:suggestion csharp_style_deconstructed_variable_declaration = true:suggestion csharp_style_unused_value_assignment_preference = discard_variable:suggestion csharp_style_unused_value_expression_statement_preference = discard_variable:silent -csharp_indent_case_contents_when_block = true csharp_prefer_static_local_function = true:suggestion csharp_style_prefer_readonly_struct = true:suggestion csharp_style_prefer_readonly_struct_member = true:suggestion @@ -70,12 +423,1368 @@ csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion csharp_style_pattern_matching_over_as_with_null_check = true:suggestion csharp_style_prefer_not_pattern = true:suggestion csharp_style_prefer_extended_property_pattern = true:suggestion -csharp_style_var_for_built_in_types = true:none +csharp_style_var_for_built_in_types = false:suggestion +resharper_accessor_owner_body = expression_body +resharper_alignment_tab_fill_style = use_spaces +resharper_align_first_arg_by_paren = true +resharper_align_linq_query = true +resharper_align_multiline_argument = true +resharper_align_multiline_array_and_object_initializer = false +resharper_align_multiline_array_initializer = true +resharper_align_multiline_binary_expressions_chain = true +resharper_align_multiline_binary_patterns = true +resharper_align_multiline_calls_chain = true +resharper_align_multiline_comments = true +resharper_align_multiline_expression = true +resharper_align_multiline_extends_list = true +resharper_align_multiline_for_stmt = false +resharper_align_multiline_implements_list = true +resharper_align_multiline_list_pattern = true +resharper_align_multiline_parameter = true +resharper_align_multiline_property_pattern = true +resharper_align_multiline_statement_conditions = true +resharper_align_multiline_switch_expression = true +resharper_align_multiple_declaration = true +resharper_align_multline_type_parameter_constrains = true +resharper_align_multline_type_parameter_list = true +resharper_align_tuple_components = true +resharper_allow_alias = true +resharper_allow_comment_after_lbrace = true +resharper_apply_auto_detected_rules = false +resharper_apply_on_completion = true +resharper_arguments_anonymous_function = positional +resharper_arguments_literal = positional +resharper_arguments_named = positional +resharper_arguments_other = positional +resharper_arguments_skip_single = false +resharper_arguments_string_literal = positional +resharper_attribute_style = do_not_touch +resharper_autodetect_indent_settings = false +resharper_blank_lines_after_block_statements = 1 +resharper_blank_lines_after_case = 0 +resharper_blank_lines_after_control_transfer_statements = 1 +resharper_blank_lines_after_file_scoped_namespace_directive = 1 +resharper_blank_lines_after_imports = 1 +resharper_blank_lines_after_multiline_statements = 0 +resharper_blank_lines_after_options = 1 +resharper_blank_lines_after_start_comment = 1 +resharper_blank_lines_after_using_list = 1 +resharper_blank_lines_around_accessor = 0 +resharper_blank_lines_around_auto_property = 1 +resharper_blank_lines_around_block_case_section = 0 +resharper_blank_lines_around_field = 1 +resharper_blank_lines_around_global_attribute = 0 +resharper_blank_lines_around_invocable = 1 +resharper_blank_lines_around_local_method = 1 +resharper_blank_lines_around_multiline_case_section = 0 +resharper_blank_lines_around_namespace = 1 +resharper_blank_lines_around_property = 1 +resharper_blank_lines_around_region = 1 +resharper_blank_lines_around_single_line_accessor = 0 +resharper_blank_lines_around_single_line_auto_property = 0 +resharper_blank_lines_around_single_line_field = 0 +resharper_blank_lines_around_single_line_invocable = 0 +resharper_blank_lines_around_single_line_local_method = 1 +resharper_blank_lines_around_single_line_property = 0 +resharper_blank_lines_around_single_line_type = 1 +resharper_blank_lines_around_type = 1 +resharper_blank_lines_before_block_statements = 0 +resharper_blank_lines_before_case = 0 +resharper_blank_lines_before_control_transfer_statements = 1 +resharper_blank_lines_before_multiline_statements = 1 +resharper_blank_lines_before_single_line_comment = 1 +resharper_blank_lines_inside_namespace = 0 +resharper_blank_lines_inside_region = 1 +resharper_blank_lines_inside_type = 0 +resharper_blank_line_after_pi = true +resharper_braces_redundant = true +resharper_builtin_type_apply_to_native_integer = true +resharper_can_use_global_alias = true +resharper_configure_await_analysis_mode = disabled +resharper_constructor_or_destructor_body = block_body +resharper_continuous_indent_multiplier = 1 +resharper_csharp_allow_far_alignment = false +resharper_csharp_insert_final_newline = true +resharper_csharp_keep_blank_lines_in_code = 1 +resharper_csharp_keep_blank_lines_in_declarations = 1 +resharper_csharp_keep_nontrivial_alias = false +resharper_csharp_max_line_length = 160 +resharper_csharp_naming_rule.enum_member = AaBb +resharper_csharp_naming_rule.method_property_event = AaBb +resharper_csharp_naming_rule.other = AaBb +resharper_csharp_space_after_unary_operator = false +resharper_csharp_use_indent_from_vs = false +resharper_csharp_wrap_arguments_style = chop_if_long +resharper_csharp_wrap_before_binary_opsign = true +resharper_csharp_wrap_lines = true +resharper_csharp_wrap_parameters_style = chop_if_long +resharper_default_exception_variable_name = e +resharper_default_value_when_type_evident = default_literal +resharper_default_value_when_type_not_evident = default_expression +resharper_disable_blank_line_changes = false +resharper_disable_formatter = false +resharper_disable_indenter = false +resharper_disable_int_align = false +resharper_disable_line_break_changes = false +resharper_disable_line_break_removal = false +resharper_disable_space_changes = false +resharper_empty_block_style = together +resharper_enforce_line_ending_style = false +resharper_event_handler_pattern_long = $object$On$event$ +resharper_event_handler_pattern_short = On$event$ +resharper_existing_test_class_name_suffixes = Test, Fixture +resharper_extra_spaces = remove_all +resharper_force_attribute_style = separate +resharper_force_chop_compound_do_expression = false +resharper_force_chop_compound_if_expression = false +resharper_force_chop_compound_while_expression = false +resharper_formatter_off_tag = +resharper_formatter_on_tag = +resharper_formatter_tags_accept_regexp = false +resharper_formatter_tags_enabled = false +resharper_format_leading_spaces_decl = false +resharper_for_built_in_types = use_var_when_evident +resharper_for_other_types = use_explicit_type +resharper_for_simple_types = use_var_when_evident +resharper_ignore_space_preservation = false +resharper_include_prefix_comment_in_indent = false +resharper_indent_anonymous_method_block = true +resharper_indent_braces_inside_statement_conditions = true +resharper_indent_case_from_select = true +resharper_indent_child_elements = OneIndent +resharper_indent_inside_namespace = true +resharper_indent_invocation_pars = inside +resharper_indent_method_decl_pars = inside +resharper_indent_nested_fixed_stmt = false +resharper_indent_nested_foreach_stmt = false +resharper_indent_nested_for_stmt = false +resharper_indent_nested_lock_stmt = false +resharper_indent_nested_usings_stmt = false +resharper_indent_nested_while_stmt = false +resharper_indent_pars = inside +resharper_indent_preprocessor_if = no_indent +resharper_indent_preprocessor_other = no_indent +resharper_indent_preprocessor_region = usual_indent +resharper_indent_primary_constructor_decl_pars = inside +resharper_indent_raw_literal_string = align +resharper_indent_statement_pars = inside +resharper_indent_text = OneIndent +resharper_indent_typearg_angles = inside +resharper_indent_typeparam_angles = inside +resharper_indent_type_constraints = true +resharper_instance_members_qualify_declared_in = this_class, base_class +resharper_int_align = false +resharper_int_align_fix_in_adjacent = true +resharper_keep_existing_attribute_arrangement = false +resharper_keep_existing_declaration_block_arrangement = false +resharper_keep_existing_declaration_parens_arrangement = true +resharper_keep_existing_embedded_arrangement = true +resharper_keep_existing_embedded_block_arrangement = false +resharper_keep_existing_enum_arrangement = false +resharper_keep_existing_expr_member_arrangement = true +resharper_keep_existing_invocation_parens_arrangement = true +resharper_keep_existing_list_patterns_arrangement = true +resharper_keep_existing_primary_constructor_declaration_parens_arrangement = true +resharper_keep_existing_property_patterns_arrangement = true +resharper_keep_existing_switch_expression_arrangement = true +resharper_keep_user_linebreaks = true +resharper_keep_user_wrapping = true +resharper_linebreaks_inside_tags_for_elements_with_child_elements = true +resharper_linebreaks_inside_tags_for_multiline_elements = true +resharper_linebreak_before_multiline_elements = true +resharper_linebreak_before_singleline_elements = false +resharper_local_function_body = block_body +resharper_max_array_initializer_elements_on_line = 10000 +resharper_max_attribute_length_for_same_line = 60 +resharper_max_enum_members_on_line = 1 +resharper_max_formal_parameters_on_line = 8 +resharper_max_initializer_elements_on_line = 4 +resharper_max_invocation_arguments_on_line = 8 +resharper_max_primary_constructor_parameters_on_line = 8 +resharper_method_or_operator_body = block_body +resharper_nested_ternary_style = autodetect +resharper_new_line_before_while = true +resharper_new_test_class_name_suffix = Tests +resharper_null_checking_pattern_style = empty_recursive_pattern +resharper_object_creation_when_type_evident = target_typed +resharper_object_creation_when_type_not_evident = explicitly_typed +resharper_old_engine = false +resharper_outdent_binary_ops = false +resharper_outdent_binary_pattern_ops = false +resharper_outdent_commas = true +resharper_outdent_dots = false +resharper_outdent_statement_labels = false +resharper_outdent_ternary_ops = false +resharper_parentheses_non_obvious_operations = none, shift, bitwise_and, bitwise_exclusive_or, bitwise_inclusive_or, bitwise +resharper_parentheses_redundancy_style = remove_if_not_clarifies_precedence +resharper_parentheses_same_type_operations = false +resharper_pi_attributes_indent = align_by_first_attribute +resharper_pi_attribute_style = do_not_touch +resharper_place_accessorholder_attribute_on_same_line = false +resharper_place_accessor_attribute_on_same_line = false +resharper_place_comments_at_first_column = false +resharper_place_constructor_initializer_on_same_line = true +resharper_place_event_attribute_on_same_line = false +resharper_place_expr_accessor_on_single_line = if_owner_is_single_line +resharper_place_expr_method_on_single_line = if_owner_is_single_line +resharper_place_expr_property_on_single_line = if_owner_is_single_line +resharper_place_field_attribute_on_same_line = false +resharper_place_linq_into_on_new_line = true +resharper_place_method_attribute_on_same_line = false +resharper_place_property_attribute_on_same_line = false +resharper_place_record_field_attribute_on_same_line = if_owner_is_single_line +resharper_place_simple_case_statement_on_same_line = false +resharper_place_simple_embedded_statement_on_same_line = false +resharper_place_simple_initializer_on_single_line = true +resharper_place_simple_list_pattern_on_single_line = true +resharper_place_simple_property_pattern_on_single_line = true +resharper_place_simple_switch_expression_on_single_line = false +resharper_place_type_attribute_on_same_line = false +resharper_place_type_constraints_on_same_line = true +resharper_prefer_explicit_discard_declaration = false +resharper_prefer_qualified_reference = false +resharper_prefer_separate_deconstructed_variables_declaration = true +resharper_qualified_using_at_nested_scope = false +resharper_remove_blank_lines_near_braces_in_code = true +resharper_remove_blank_lines_near_braces_in_declarations = true +resharper_remove_only_unused_aliases = true +resharper_remove_unused_only_aliases = false +resharper_resx_allow_far_alignment = false +resharper_resx_attribute_indent = single_indent +resharper_resx_insert_final_newline = false +resharper_resx_linebreaks_inside_tags_for_elements_longer_than = 2147483647 +resharper_resx_linebreak_before_elements = +resharper_resx_max_blank_lines_between_tags = 0 +resharper_resx_max_line_length = 2147483647 +resharper_resx_space_before_self_closing = false +resharper_resx_use_indent_from_vs = true +resharper_resx_wrap_lines = false +resharper_resx_wrap_tags_and_pi = false +resharper_resx_wrap_text = false +resharper_show_autodetect_configure_formatting_tip = true +resharper_sort_usings = true +resharper_sort_usings_lowercase_first = false +resharper_spaces_around_eq_in_attribute = false +resharper_spaces_around_eq_in_pi_attribute = false +resharper_spaces_inside_tags = false +resharper_space_after_attributes = true +resharper_space_after_attribute_target_colon = true +resharper_space_after_colon = true +resharper_space_after_colon_in_case = true +resharper_space_after_comma = true +resharper_space_after_last_attribute = false +resharper_space_after_last_pi_attribute = false +resharper_space_after_operator_keyword = true +resharper_space_after_triple_slash = true +resharper_space_after_type_parameter_constraint_colon = true +resharper_space_around_additive_op = true +resharper_space_around_alias_eq = true +resharper_space_around_assignment_op = true +resharper_space_around_lambda_arrow = true +resharper_space_around_member_access_operator = false +resharper_space_around_relational_op = true +resharper_space_around_shift_op = true +resharper_space_around_stmt_colon = true +resharper_space_around_ternary_operator = true +resharper_space_before_array_rank_parentheses = false +resharper_space_before_attribute_target_colon = false +resharper_space_before_checked_parentheses = true +resharper_space_before_colon = false +resharper_space_before_colon_in_case = false +resharper_space_before_comma = false +resharper_space_before_default_parentheses = true +resharper_space_before_empty_invocation_parentheses = false +resharper_space_before_invocation_parentheses = false +resharper_space_before_label_colon = false +resharper_space_before_nameof_parentheses = true +resharper_space_before_new_parentheses = true +resharper_space_before_nullable_mark = false +resharper_space_before_pointer_asterik_declaration = false +resharper_space_before_semicolon = false +resharper_space_before_singleline_accessorholder = true +resharper_space_before_sizeof_parentheses = true +resharper_space_before_trailing_comment = true +resharper_space_before_typeof_parentheses = true +resharper_space_before_type_argument_angle = false +resharper_space_before_type_parameter_angle = false +resharper_space_before_type_parameter_constraint_colon = true +resharper_space_before_type_parameter_parentheses = true +resharper_space_between_accessors_in_singleline_property = true +resharper_space_between_attribute_sections = true +resharper_space_between_keyword_and_expression = true +resharper_space_between_keyword_and_type = true +resharper_space_in_singleline_accessorholder = true +resharper_space_in_singleline_anonymous_method = true +resharper_space_in_singleline_method = true +resharper_space_near_postfix_and_prefix_op = false +resharper_space_within_array_initialization_braces = false +resharper_space_within_array_rank_empty_parentheses = false +resharper_space_within_array_rank_parentheses = false +resharper_space_within_attribute_angles = false +resharper_space_within_checked_parentheses = false +resharper_space_within_default_parentheses = false +resharper_space_within_empty_braces = true +resharper_space_within_empty_invocation_parentheses = false +resharper_space_within_empty_method_parentheses = false +resharper_space_within_expression_parentheses = false +resharper_space_within_invocation_parentheses = false +resharper_space_within_method_parentheses = false +resharper_space_within_nameof_parentheses = false +resharper_space_within_new_parentheses = false +resharper_space_within_single_line_array_initializer_braces = true +resharper_space_within_sizeof_parentheses = false +resharper_space_within_slice_pattern = true +resharper_space_within_tuple_parentheses = false +resharper_space_within_typeof_parentheses = false +resharper_space_within_type_argument_angles = false +resharper_space_within_type_parameter_angles = false +resharper_space_within_type_parameter_parentheses = false +resharper_special_else_if_treatment = true +resharper_static_members_qualify_members = none +resharper_static_members_qualify_with = declared_type +resharper_stick_comment = true +resharper_support_vs_event_naming_pattern = true +resharper_tests_project_name = +resharper_tests_project_sub_namespace = +resharper_trailing_comma_in_multiline_lists = false +resharper_trailing_comma_in_singleline_lists = false +resharper_use_continuous_indent_inside_initializer_braces = true +resharper_use_continuous_indent_inside_parens = true +resharper_use_heuristics_for_body_style = true +resharper_use_indents_from_main_language_in_file = true +resharper_use_indent_from_previous_element = true +resharper_use_roslyn_logic_for_evident_types = false +resharper_vb_allow_far_alignment = false +resharper_vb_insert_final_newline = false +resharper_vb_keep_blank_lines_in_code = 2 +resharper_vb_keep_blank_lines_in_declarations = 2 +resharper_vb_keep_nontrivial_alias = true +resharper_vb_max_line_length = 120 +resharper_vb_place_field_attribute_on_same_line = true +resharper_vb_place_method_attribute_on_same_line = false +resharper_vb_place_type_attribute_on_same_line = false +resharper_vb_space_after_unary_operator = true +resharper_vb_space_around_multiplicative_op = false +resharper_vb_space_before_empty_method_parentheses = false +resharper_vb_space_before_method_parentheses = false +resharper_vb_use_indent_from_vs = true +resharper_vb_wrap_arguments_style = wrap_if_long +resharper_vb_wrap_before_binary_opsign = false +resharper_vb_wrap_lines = true +resharper_vb_wrap_parameters_style = wrap_if_long +resharper_wrap_after_declaration_lpar = true +resharper_wrap_after_dot_in_method_calls = false +resharper_wrap_after_invocation_lpar = true +resharper_wrap_after_primary_constructor_declaration_lpar = true +resharper_wrap_after_property_in_chained_method_calls = false +resharper_wrap_around_elements = true +resharper_wrap_array_initializer_style = wrap_if_long +resharper_wrap_before_arrow_with_expressions = false +resharper_wrap_before_binary_pattern_op = true +resharper_wrap_before_comma = false +resharper_wrap_before_declaration_lpar = false +resharper_wrap_before_declaration_rpar = true +resharper_wrap_before_eq = false +resharper_wrap_before_extends_colon = false +resharper_wrap_before_first_method_call = false +resharper_wrap_before_first_type_parameter_constraint = true +resharper_wrap_before_invocation_lpar = false +resharper_wrap_before_invocation_rpar = false +resharper_wrap_before_linq_expression = false +resharper_wrap_before_primary_constructor_declaration_lpar = false +resharper_wrap_before_primary_constructor_declaration_rpar = false +resharper_wrap_before_ternary_opsigns = true +resharper_wrap_before_type_parameter_langle = true +resharper_wrap_chained_binary_expressions = chop_if_long +resharper_wrap_chained_binary_patterns = chop_if_long +resharper_wrap_chained_method_calls = chop_if_long +resharper_wrap_enum_declaration = chop_always +resharper_wrap_extends_list_style = wrap_if_long +resharper_wrap_for_stmt_header_style = chop_if_long +resharper_wrap_list_pattern = chop_if_long +resharper_wrap_multiple_declaration_style = chop_if_long +resharper_wrap_multiple_type_parameter_constraints_style = chop_if_long +resharper_wrap_object_and_collection_initializer_style = chop_if_long +resharper_wrap_primary_constructor_parameters_style = chop_if_long +resharper_wrap_property_pattern = chop_if_long +resharper_wrap_switch_expression = chop_always +resharper_wrap_ternary_expr_style = chop_if_long +resharper_wrap_verbatim_interpolated_strings = no_wrap +resharper_xmldoc_allow_far_alignment = true +resharper_xmldoc_attribute_indent = align_by_first_attribute +resharper_xmldoc_insert_final_newline = false +resharper_xmldoc_linebreaks_inside_tags_for_elements_longer_than = 120 +resharper_xmldoc_linebreak_before_elements = summary,remarks,example,returns,param,typeparam,value,para +resharper_xmldoc_max_blank_lines_between_tags = 0 +resharper_xmldoc_max_line_length = 120 +resharper_xmldoc_space_before_self_closing = false +resharper_xmldoc_use_indent_from_vs = false +resharper_xmldoc_wrap_lines = true +resharper_xmldoc_wrap_tags_and_pi = true +resharper_xmldoc_wrap_text = true +resharper_xml_allow_far_alignment = false +resharper_xml_attribute_indent = align_by_first_attribute +resharper_xml_insert_final_newline = false +resharper_xml_linebreaks_inside_tags_for_elements_longer_than = 2147483647 +resharper_xml_linebreak_before_elements = +resharper_xml_max_blank_lines_between_tags = 2 +resharper_xml_max_line_length = 120 +resharper_xml_space_before_self_closing = true +resharper_xml_use_indent_from_vs = true +resharper_xml_wrap_lines = true +resharper_xml_wrap_tags_and_pi = true +resharper_xml_wrap_text = false + +# ReSharper inspection severities +resharper_access_rights_in_text_highlighting = warning +resharper_access_to_disposed_closure_highlighting = warning +resharper_access_to_for_each_variable_in_closure_highlighting = warning +resharper_access_to_modified_closure_highlighting = warning +resharper_access_to_static_member_via_derived_type_highlighting = warning +resharper_address_of_marshal_by_ref_object_highlighting = warning +resharper_all_underscore_local_parameter_name_highlighting = warning +resharper_annotate_can_be_null_parameter_highlighting = none +resharper_annotate_can_be_null_type_member_highlighting = none +resharper_annotate_not_null_parameter_highlighting = none +resharper_annotate_not_null_type_member_highlighting = none +resharper_annotation_conflict_in_hierarchy_highlighting = warning +resharper_annotation_redundancy_at_value_type_highlighting = warning +resharper_annotation_redundancy_in_hierarchy_highlighting = warning +resharper_append_to_collection_expression_highlighting = suggestion +resharper_arguments_style_anonymous_function_highlighting = none +resharper_arguments_style_literal_highlighting = none +resharper_arguments_style_named_expression_highlighting = none +resharper_arguments_style_other_highlighting = none +resharper_arguments_style_string_literal_highlighting = none +resharper_argument_exception_constructor_argument_highlighting = warning +resharper_arrange_accessor_owner_body_highlighting = suggestion +resharper_arrange_attributes_highlighting = suggestion +resharper_arrange_constructor_or_destructor_body_highlighting = error +resharper_arrange_default_value_when_type_evident_highlighting = suggestion +resharper_arrange_default_value_when_type_not_evident_highlighting = suggestion +resharper_arrange_local_function_body_highlighting = warning +resharper_arrange_method_or_operator_body_highlighting = hint +resharper_arrange_null_checking_pattern_highlighting = error +resharper_arrange_object_creation_when_type_evident_highlighting = suggestion +resharper_arrange_object_creation_when_type_not_evident_highlighting = warning +resharper_arrange_redundant_parentheses_highlighting = warning +resharper_arrange_static_member_qualifier_highlighting = warning +resharper_arrange_trailing_comma_in_multiline_lists_highlighting = hint +resharper_arrange_trailing_comma_in_singleline_lists_highlighting = hint +resharper_arrange_var_keywords_in_deconstructing_declaration_highlighting = suggestion +resharper_array_with_default_values_initialization_highlighting = suggestion +resharper_assignment_instead_of_discard_highlighting = warning +resharper_assignment_in_conditional_expression_highlighting = warning +resharper_assignment_is_fully_discarded_highlighting = warning +resharper_assign_null_to_not_null_attribute_highlighting = warning +resharper_async_iterator_invocation_without_await_foreach_highlighting = warning +resharper_async_void_function_expression_highlighting = warning +resharper_async_void_lambda_highlighting = warning +resharper_async_void_method_highlighting = none +resharper_auto_property_can_be_made_get_only_global_highlighting = suggestion +resharper_auto_property_can_be_made_get_only_local_highlighting = suggestion +resharper_avoid_async_void_highlighting = warning +resharper_bad_attribute_brackets_spaces_highlighting = none +resharper_bad_braces_spaces_highlighting = none +resharper_bad_child_statement_indent_highlighting = warning +resharper_bad_colon_spaces_highlighting = none +resharper_bad_comma_spaces_highlighting = none +resharper_bad_control_braces_indent_highlighting = suggestion +resharper_bad_control_braces_line_breaks_highlighting = none +resharper_bad_declaration_braces_indent_highlighting = none +resharper_bad_declaration_braces_line_breaks_highlighting = none +resharper_bad_empty_braces_line_breaks_highlighting = none +resharper_bad_expression_braces_indent_highlighting = none +resharper_bad_expression_braces_line_breaks_highlighting = none +resharper_bad_generic_brackets_spaces_highlighting = none +resharper_bad_indent_highlighting = none +resharper_bad_linq_line_breaks_highlighting = none +resharper_bad_list_line_breaks_highlighting = none +resharper_bad_member_access_spaces_highlighting = none +resharper_bad_namespace_braces_indent_highlighting = none +resharper_bad_parens_line_breaks_highlighting = none +resharper_bad_parens_spaces_highlighting = none +resharper_bad_preprocessor_indent_highlighting = none +resharper_bad_semicolon_spaces_highlighting = none +resharper_bad_spaces_after_keyword_highlighting = none +resharper_bad_square_brackets_spaces_highlighting = none +resharper_bad_switch_braces_indent_highlighting = none +resharper_bad_symbol_spaces_highlighting = none +resharper_base_member_has_params_highlighting = warning +resharper_base_method_call_with_default_parameter_highlighting = warning +resharper_base_object_equals_is_object_equals_highlighting = warning +resharper_base_object_get_hash_code_call_in_get_hash_code_highlighting = warning +resharper_bitwise_operator_on_enum_without_flags_highlighting = warning +resharper_by_ref_argument_is_volatile_field_highlighting = warning +resharper_cannot_apply_equality_operator_to_type_highlighting = warning +resharper_can_simplify_dictionary_lookup_with_try_add_highlighting = suggestion +resharper_can_simplify_dictionary_lookup_with_try_get_value_highlighting = suggestion +resharper_can_simplify_dictionary_removing_with_single_call_highlighting = suggestion +resharper_can_simplify_dictionary_try_get_value_with_get_value_or_default_highlighting = suggestion +resharper_can_simplify_set_adding_with_single_call_highlighting = suggestion +resharper_catch_all_clause_highlighting = suggestion +resharper_catch_clause_without_variable_highlighting = suggestion +resharper_check_for_reference_equality_instead_1_highlighting = suggestion +resharper_check_for_reference_equality_instead_2_highlighting = suggestion +resharper_check_for_reference_equality_instead_3_highlighting = suggestion +resharper_check_for_reference_equality_instead_4_highlighting = suggestion +resharper_check_namespace_highlighting = warning +resharper_class_cannot_be_instantiated_highlighting = warning +resharper_class_can_be_sealed_global_highlighting = none +resharper_class_can_be_sealed_local_highlighting = none +resharper_class_never_instantiated_global_highlighting = suggestion +resharper_class_never_instantiated_local_highlighting = suggestion +resharper_class_with_virtual_members_never_inherited_global_highlighting = suggestion +resharper_class_with_virtual_members_never_inherited_local_highlighting = suggestion +resharper_collection_never_queried_global_highlighting = warning +resharper_collection_never_queried_local_highlighting = warning +resharper_collection_never_updated_global_highlighting = warning +resharper_collection_never_updated_local_highlighting = warning +resharper_comment_typo_highlighting = suggestion +resharper_compare_non_constrained_generic_with_null_highlighting = warning +resharper_compare_of_floats_by_equality_operator_highlighting = warning +resharper_conditional_access_qualifier_is_non_nullable_according_to_api_contract_highlighting = warning +resharper_conditional_annotation_highlighting = hint +resharper_conditional_invocation_highlighting = hint +resharper_conditional_ternary_equal_branch_highlighting = warning +resharper_condition_is_always_true_or_false_according_to_nullable_api_contract_highlighting = warning +resharper_condition_is_always_true_or_false_highlighting = warning +resharper_conflicting_annotation_highlighting = warning +resharper_confusing_char_as_integer_in_constructor_highlighting = warning +resharper_constant_conditional_access_qualifier_highlighting = warning +resharper_constant_null_coalescing_condition_highlighting = warning +resharper_constructor_initializer_loop_highlighting = warning +resharper_constructor_with_must_dispose_resource_attribute_base_is_not_annotated_highlighting = warning +resharper_container_annotation_redundancy_highlighting = warning +resharper_context_value_is_provided_highlighting = none +resharper_contract_annotation_not_parsed_highlighting = warning +resharper_convert_closure_to_method_group_highlighting = suggestion +resharper_convert_conditional_ternary_expression_to_switch_expression_highlighting = hint +resharper_convert_constructor_to_member_initializers_highlighting = suggestion +resharper_convert_if_do_to_while_highlighting = suggestion +resharper_convert_if_statement_to_conditional_ternary_expression_highlighting = suggestion +resharper_convert_if_statement_to_null_coalescing_assignment_highlighting = suggestion +resharper_convert_if_statement_to_null_coalescing_expression_highlighting = suggestion +resharper_convert_if_statement_to_return_statement_highlighting = hint +resharper_convert_if_statement_to_switch_statement_highlighting = hint +resharper_convert_if_to_or_expression_highlighting = suggestion +resharper_convert_nullable_to_short_form_highlighting = suggestion +resharper_convert_switch_statement_to_switch_expression_highlighting = hint +resharper_convert_to_auto_property_highlighting = suggestion +resharper_convert_to_auto_property_when_possible_highlighting = hint +resharper_convert_to_auto_property_with_private_setter_highlighting = hint +resharper_convert_to_compound_assignment_highlighting = hint +resharper_convert_to_constant_global_highlighting = hint +resharper_convert_to_constant_local_highlighting = hint +resharper_convert_to_lambda_expression_highlighting = suggestion +resharper_convert_to_local_function_highlighting = suggestion +resharper_convert_to_null_coalescing_compound_assignment_highlighting = suggestion +resharper_convert_to_primary_constructor_highlighting = suggestion +resharper_convert_to_static_class_highlighting = suggestion +resharper_convert_to_using_declaration_highlighting = suggestion +resharper_convert_to_vb_auto_property_highlighting = suggestion +resharper_convert_to_vb_auto_property_when_possible_highlighting = hint +resharper_convert_to_vb_auto_property_with_private_setter_highlighting = hint +resharper_convert_type_check_pattern_to_null_check_highlighting = warning +resharper_convert_type_check_to_null_check_highlighting = warning +resharper_co_variant_array_conversion_highlighting = warning +resharper_default_value_attribute_for_optional_parameter_highlighting = warning +resharper_dispose_on_using_variable_highlighting = warning +resharper_double_negation_in_pattern_highlighting = suggestion +resharper_double_negation_operator_highlighting = suggestion +resharper_duplicate_resource_highlighting = warning +resharper_empty_constructor_highlighting = warning +resharper_empty_destructor_highlighting = warning +resharper_empty_embedded_statement_highlighting = warning +resharper_empty_for_statement_highlighting = warning +resharper_empty_general_catch_clause_highlighting = warning +resharper_empty_namespace_highlighting = warning +resharper_empty_region_highlighting = suggestion +resharper_empty_statement_highlighting = warning +resharper_enforce_do_while_statement_braces_highlighting = error +resharper_enforce_fixed_statement_braces_highlighting = error +resharper_enforce_foreach_statement_braces_highlighting = error +resharper_enforce_for_statement_braces_highlighting = error +resharper_enforce_if_statement_braces_highlighting = error +resharper_enforce_lock_statement_braces_highlighting = error +resharper_enforce_using_statement_braces_highlighting = error +resharper_enforce_while_statement_braces_highlighting = error +resharper_entity_framework_model_validation_circular_dependency_highlighting = hint +resharper_entity_framework_model_validation_unlimited_string_length_highlighting = warning +resharper_entity_name_captured_only_global_highlighting = warning +resharper_entity_name_captured_only_local_highlighting = warning +resharper_enumerable_sum_in_explicit_unchecked_context_highlighting = warning +resharper_enum_underlying_type_is_int_highlighting = warning +resharper_equal_expression_comparison_highlighting = warning +resharper_event_exception_not_documented_highlighting = suggestion +resharper_event_never_invoked_global_highlighting = suggestion +resharper_event_never_subscribed_to_global_highlighting = suggestion +resharper_event_never_subscribed_to_local_highlighting = suggestion +resharper_event_unsubscription_via_anonymous_delegate_highlighting = warning +resharper_exception_not_documented_highlighting = warning +resharper_exception_not_documented_optional_highlighting = hint +resharper_exception_not_thrown_highlighting = warning +resharper_exception_not_thrown_optional_highlighting = hint +resharper_explicit_caller_info_argument_highlighting = warning +resharper_expression_is_always_null_highlighting = warning +resharper_extract_common_property_pattern_highlighting = hint +resharper_field_can_be_made_read_only_global_highlighting = suggestion +resharper_field_can_be_made_read_only_local_highlighting = suggestion +resharper_field_hides_interface_property_with_default_implementation_highlighting = warning +resharper_foreach_can_be_converted_to_query_using_another_get_enumerator_highlighting = hint +resharper_foreach_can_be_partly_converted_to_query_using_another_get_enumerator_highlighting = hint +resharper_format_string_placeholders_mismatch_highlighting = warning +resharper_format_string_problem_highlighting = warning +resharper_for_can_be_converted_to_foreach_highlighting = suggestion +resharper_for_statement_condition_is_true_highlighting = warning +resharper_function_complexity_overflow_highlighting = none +resharper_function_never_returns_highlighting = warning +resharper_function_recursive_on_all_paths_highlighting = warning +resharper_gc_suppress_finalize_for_type_without_destructor_highlighting = warning +resharper_grammar_mistake_in_comment_highlighting = suggestion +resharper_grammar_mistake_in_markup_attribute_highlighting = suggestion +resharper_grammar_mistake_in_markup_text_highlighting = suggestion +resharper_grammar_mistake_in_string_literal_highlighting = none +resharper_heap_view_boxing_allocation_highlighting = hint +resharper_heap_view_can_avoid_closure_highlighting = suggestion +resharper_heap_view_closure_allocation_highlighting = hint +resharper_heap_view_delegate_allocation_highlighting = hint +resharper_heap_view_implicit_capture_highlighting = none +resharper_heap_view_object_allocation_evident_highlighting = hint +resharper_heap_view_object_allocation_highlighting = hint +resharper_heap_view_object_allocation_possible_highlighting = hint +resharper_heap_view_possible_boxing_allocation_highlighting = hint +resharper_heuristic_unreachable_code_highlighting = warning +resharper_identifier_typo_highlighting = suggestion +resharper_implement_comparison_operators_for_classes_highlighting = suggestion +resharper_implement_comparison_operators_for_records_highlighting = suggestion +resharper_implement_comparison_operators_for_structs_highlighting = suggestion +resharper_implement_equality_operators_for_classes_highlighting = suggestion +resharper_implement_equality_operators_for_records_highlighting = suggestion +resharper_implement_equality_operators_for_structs_highlighting = suggestion +resharper_implement_equatable_highlighting = warning +resharper_inactive_preprocessor_branch_highlighting = warning +resharper_inconsistently_synchronized_field_highlighting = warning +resharper_inconsistent_naming_highlighting = warning +resharper_inconsistent_order_of_locks_highlighting = warning +resharper_incorrect_blank_lines_near_braces_highlighting = none +resharper_indexing_by_invalid_range_highlighting = warning +resharper_inheritdoc_consider_usage_highlighting = none +resharper_inheritdoc_invalid_usage_highlighting = warning +resharper_inline_out_variable_declaration_highlighting = suggestion +resharper_inline_temporary_variable_highlighting = hint +resharper_intentional_blocking_attempt_highlighting = warning +resharper_internal_constructor_visibility_highlighting = suggestion +resharper_internal_or_private_member_not_documented_highlighting = none +resharper_interpolated_string_expression_is_not_i_formattable_highlighting = warning +resharper_introduce_optional_parameters_global_highlighting = suggestion +resharper_introduce_optional_parameters_local_highlighting = suggestion +resharper_int_division_by_zero_highlighting = warning +resharper_int_variable_overflow_highlighting = warning +resharper_int_variable_overflow_in_checked_context_highlighting = warning +resharper_int_variable_overflow_in_unchecked_context_highlighting = warning +resharper_invalid_value_range_boundary_highlighting = warning +resharper_invalid_value_type_highlighting = warning +resharper_invalid_xml_doc_comment_highlighting = warning +resharper_invert_condition_1_highlighting = hint +resharper_invert_if_highlighting = hint +resharper_invocation_is_skipped_highlighting = hint +resharper_invoke_as_extension_method_highlighting = suggestion +resharper_in_parameter_with_must_dispose_resource_attribute_highlighting = warning +resharper_is_expression_always_false_highlighting = warning +resharper_is_expression_always_true_highlighting = warning +resharper_iterator_method_result_is_ignored_highlighting = warning +resharper_iterator_never_returns_highlighting = warning +resharper_join_declaration_and_initializer_highlighting = suggestion +resharper_join_null_check_with_usage_highlighting = suggestion +resharper_lambda_expression_can_be_made_static_highlighting = none +resharper_lambda_expression_must_be_static_highlighting = suggestion +resharper_lambda_should_not_capture_context_highlighting = warning +resharper_localizable_element_highlighting = warning +resharper_local_function_can_be_made_static_highlighting = none +resharper_local_function_hides_method_highlighting = warning +resharper_local_suppression_highlighting = warning +resharper_local_variable_hides_member_highlighting = warning +resharper_local_variable_hides_primary_constructor_parameter_highlighting = warning +resharper_lock_on_object_with_weak_identity_highlighting = warning +resharper_long_literal_ending_lower_l_highlighting = warning +resharper_loop_can_be_converted_to_query_highlighting = hint +resharper_loop_can_be_partly_converted_to_query_highlighting = none +resharper_loop_variable_is_never_changed_inside_loop_highlighting = warning +resharper_markup_attribute_typo_highlighting = suggestion +resharper_markup_text_typo_highlighting = suggestion +resharper_math_abs_method_is_redundant_highlighting = warning +resharper_math_clamp_min_greater_than_max_highlighting = warning +resharper_meaningless_default_parameter_value_highlighting = warning +resharper_member_can_be_file_local_highlighting = none +resharper_member_can_be_internal_highlighting = none +resharper_member_can_be_made_static_global_highlighting = hint +resharper_member_can_be_made_static_local_highlighting = hint +resharper_member_can_be_private_global_highlighting = suggestion +resharper_member_can_be_private_local_highlighting = suggestion +resharper_member_can_be_protected_global_highlighting = suggestion +resharper_member_can_be_protected_local_highlighting = suggestion +resharper_member_hides_interface_member_with_default_implementation_highlighting = warning +resharper_member_hides_static_from_outer_class_highlighting = warning +resharper_member_initializer_value_ignored_highlighting = warning +resharper_merge_and_pattern_highlighting = suggestion +resharper_merge_cast_with_type_check_highlighting = suggestion +resharper_merge_conditional_expression_highlighting = suggestion +resharper_merge_into_logical_pattern_highlighting = hint +resharper_merge_into_negated_pattern_highlighting = hint +resharper_merge_into_pattern_highlighting = suggestion +resharper_merge_nested_property_patterns_highlighting = suggestion +resharper_merge_sequential_checks_highlighting = hint +resharper_method_has_async_overload_highlighting = suggestion +resharper_method_has_async_overload_with_cancellation_highlighting = suggestion +resharper_method_overload_with_optional_parameter_highlighting = warning +resharper_method_supports_cancellation_highlighting = suggestion +resharper_missing_annotation_highlighting = warning +resharper_missing_blank_lines_highlighting = none +resharper_missing_indent_highlighting = none +resharper_missing_linebreak_highlighting = none +resharper_missing_space_highlighting = none +resharper_missing_suppression_justification_highlighting = warning +resharper_missing_xml_doc_highlighting = warning +resharper_more_specific_foreach_variable_type_available_highlighting = suggestion +resharper_move_local_function_after_jump_statement_highlighting = hint +resharper_move_to_existing_positional_deconstruction_pattern_highlighting = hint +resharper_move_variable_declaration_inside_loop_condition_highlighting = suggestion +resharper_multiple_nullable_attributes_usage_highlighting = warning +resharper_multiple_order_by_highlighting = warning +resharper_multiple_resolve_candidates_in_text_highlighting = warning +resharper_multiple_spaces_highlighting = none +resharper_multiple_statements_on_one_line_highlighting = none +resharper_multiple_type_members_on_one_line_highlighting = none +resharper_must_use_return_value_highlighting = warning +resharper_negation_of_relational_pattern_highlighting = suggestion +resharper_negative_equality_expression_highlighting = suggestion +resharper_negative_index_highlighting = warning +resharper_nested_string_interpolation_highlighting = suggestion +resharper_non_atomic_compound_operator_highlighting = warning +resharper_non_constant_equality_expression_has_constant_result_highlighting = warning +resharper_non_parsable_element_highlighting = warning +resharper_non_readonly_member_in_get_hash_code_highlighting = warning +resharper_non_volatile_field_in_double_check_locking_highlighting = warning +resharper_notify_property_changed_invocator_from_constructor_highlighting = warning +resharper_not_accessed_field_global_highlighting = suggestion +resharper_not_accessed_field_local_highlighting = warning +resharper_not_accessed_out_parameter_variable_highlighting = warning +resharper_not_accessed_positional_property_global_highlighting = warning +resharper_not_accessed_positional_property_local_highlighting = warning +resharper_not_accessed_variable_highlighting = warning +resharper_not_allowed_annotation_highlighting = warning +resharper_not_assigned_out_parameter_highlighting = warning +resharper_not_declared_in_parent_culture_highlighting = warning +resharper_not_disposed_resource_highlighting = warning +resharper_not_disposed_resource_is_returned_by_property_highlighting = warning +resharper_not_disposed_resource_is_returned_highlighting = warning +resharper_not_null_or_required_member_is_not_initialized_highlighting = warning +resharper_not_observable_annotation_redundancy_highlighting = warning +resharper_not_overridden_in_specific_culture_highlighting = warning +resharper_not_resolved_in_text_highlighting = warning +resharper_nullable_warning_suppression_is_used_highlighting = none +resharper_nullness_annotation_conflict_with_jet_brains_annotations_highlighting = warning +resharper_null_coalescing_condition_is_always_not_null_according_to_api_contract_highlighting = warning +resharper_n_unit_async_method_must_be_task_highlighting = warning +resharper_n_unit_attribute_produces_too_many_tests_highlighting = none +resharper_n_unit_auto_fixture_incorrect_argument_type_highlighting = warning +resharper_n_unit_auto_fixture_missed_test_attribute_highlighting = warning +resharper_n_unit_auto_fixture_missed_test_or_test_fixture_attribute_highlighting = warning +resharper_n_unit_auto_fixture_redundant_argument_in_inline_auto_data_attribute_highlighting = warning +resharper_n_unit_duplicate_values_highlighting = warning +resharper_n_unit_ignored_parameter_attribute_highlighting = warning +resharper_n_unit_implicit_unspecified_null_values_highlighting = warning +resharper_n_unit_incorrect_argument_type_highlighting = warning +resharper_n_unit_incorrect_expected_result_type_highlighting = warning +resharper_n_unit_incorrect_range_bounds_highlighting = warning +resharper_n_unit_method_with_parameters_and_test_attribute_highlighting = warning +resharper_n_unit_missing_arguments_in_test_case_attribute_highlighting = warning +resharper_n_unit_non_public_method_with_test_attribute_highlighting = warning +resharper_n_unit_no_values_provided_highlighting = warning +resharper_n_unit_parameter_type_is_not_compatible_with_attribute_highlighting = warning +resharper_n_unit_range_attribute_bounds_are_out_of_range_highlighting = warning +resharper_n_unit_range_step_sign_mismatch_highlighting = warning +resharper_n_unit_range_step_value_must_not_be_zero_highlighting = warning +resharper_n_unit_range_to_value_is_not_reachable_highlighting = warning +resharper_n_unit_redundant_argument_instead_of_expected_result_highlighting = warning +resharper_n_unit_redundant_argument_in_test_case_attribute_highlighting = warning +resharper_n_unit_redundant_expected_result_in_test_case_attribute_highlighting = warning +resharper_n_unit_test_case_attribute_requires_expected_result_highlighting = warning +resharper_n_unit_test_case_result_property_duplicates_expected_result_highlighting = warning +resharper_n_unit_test_case_result_property_is_obsolete_highlighting = warning +resharper_n_unit_test_case_source_cannot_be_resolved_highlighting = warning +resharper_n_unit_test_case_source_must_be_field_property_method_highlighting = warning +resharper_n_unit_test_case_source_must_be_static_highlighting = warning +resharper_n_unit_test_case_source_should_implement_i_enumerable_highlighting = warning +resharper_object_creation_as_statement_highlighting = warning +resharper_obsolete_element_error_highlighting = error +resharper_obsolete_element_highlighting = warning +resharper_one_way_operation_contract_with_return_type_highlighting = warning +resharper_operation_contract_without_service_contract_highlighting = warning +resharper_operator_is_can_be_used_highlighting = warning +resharper_operator_without_matched_checked_operator_highlighting = warning +resharper_optional_parameter_hierarchy_mismatch_highlighting = warning +resharper_optional_parameter_ref_out_highlighting = warning +resharper_outdent_is_off_prev_level_highlighting = none +resharper_out_parameter_value_is_always_discarded_global_highlighting = suggestion +resharper_out_parameter_value_is_always_discarded_local_highlighting = warning +resharper_overridden_with_empty_value_highlighting = warning +resharper_overridden_with_same_value_highlighting = suggestion +resharper_override_equals_highlighting = warning +resharper_parameter_hides_member_highlighting = warning +resharper_parameter_hides_primary_constructor_parameter_highlighting = warning +resharper_parameter_only_used_for_precondition_check_global_highlighting = suggestion +resharper_parameter_only_used_for_precondition_check_local_highlighting = warning +resharper_parameter_type_can_be_enumerable_global_highlighting = hint +resharper_parameter_type_can_be_enumerable_local_highlighting = hint +resharper_partial_method_parameter_name_mismatch_highlighting = warning +resharper_partial_method_with_single_part_highlighting = warning +resharper_partial_type_with_single_part_highlighting = warning +resharper_pass_string_interpolation_highlighting = hint +resharper_pattern_always_matches_highlighting = warning +resharper_pattern_is_always_true_or_false_highlighting = warning +resharper_pattern_is_redundant_highlighting = warning +resharper_pattern_never_matches_highlighting = warning +resharper_place_assignment_expression_into_block_highlighting = none +resharper_polymorphic_field_like_event_invocation_highlighting = warning +resharper_possible_infinite_inheritance_highlighting = warning +resharper_possible_intended_rethrow_highlighting = warning +resharper_possible_interface_member_ambiguity_highlighting = warning +resharper_possible_invalid_cast_exception_highlighting = warning +resharper_possible_invalid_cast_exception_in_foreach_loop_highlighting = warning +resharper_possible_invalid_operation_exception_highlighting = warning +resharper_possible_loss_of_fraction_highlighting = warning +resharper_possible_mistaken_argument_highlighting = warning +resharper_possible_mistaken_call_to_get_type_1_highlighting = warning +resharper_possible_mistaken_call_to_get_type_2_highlighting = warning +resharper_possible_multiple_consumption_highlighting = warning +resharper_possible_multiple_enumeration_highlighting = warning +resharper_possible_multiple_write_access_in_double_check_locking_highlighting = warning +resharper_possible_null_reference_exception_highlighting = warning +resharper_possible_struct_member_modification_of_non_variable_struct_highlighting = warning +resharper_possible_unintended_linear_search_in_set_highlighting = warning +resharper_possible_unintended_queryable_as_enumerable_highlighting = suggestion +resharper_possible_unintended_reference_comparison_highlighting = warning +resharper_possible_write_to_me_highlighting = warning +resharper_possibly_impure_method_call_on_readonly_variable_highlighting = warning +resharper_possibly_missing_indexer_initializer_comma_highlighting = warning +resharper_possibly_mistaken_use_of_interpolated_string_insert_highlighting = warning +resharper_possibly_unintended_usage_parameterless_get_expression_type_highlighting = error +resharper_private_field_can_be_converted_to_local_variable_highlighting = warning +resharper_property_can_be_made_init_only_global_highlighting = suggestion +resharper_property_can_be_made_init_only_local_highlighting = suggestion +resharper_property_field_keyword_is_never_assigned_highlighting = warning +resharper_property_field_keyword_is_never_used_highlighting = warning +resharper_property_not_resolved_highlighting = error +resharper_public_constructor_in_abstract_class_highlighting = suggestion +resharper_pure_attribute_on_void_method_highlighting = warning +resharper_raw_string_can_be_simplified_highlighting = hint +resharper_read_access_in_double_check_locking_highlighting = warning +resharper_redundant_abstract_modifier_highlighting = warning +resharper_redundant_accessor_body_highlighting = suggestion +resharper_redundant_always_match_subpattern_highlighting = suggestion +resharper_redundant_annotation_argument_highlighting = suggestion +resharper_redundant_annotation_highlighting = suggestion +resharper_redundant_anonymous_type_property_name_highlighting = warning +resharper_redundant_argument_default_value_highlighting = warning +resharper_redundant_array_creation_expression_highlighting = hint +resharper_redundant_array_lower_bound_specification_highlighting = warning +resharper_redundant_assertion_statement_highlighting = suggestion +resharper_redundant_assignment_highlighting = warning +resharper_redundant_attribute_parentheses_highlighting = hint +resharper_redundant_attribute_suffix_highlighting = warning +resharper_redundant_attribute_usage_property_highlighting = suggestion +resharper_redundant_base_constructor_call_highlighting = warning +resharper_redundant_blank_lines_highlighting = none +resharper_redundant_bool_compare_highlighting = warning +resharper_redundant_caller_argument_expression_default_value_highlighting = warning +resharper_redundant_captured_context_highlighting = suggestion +resharper_redundant_case_label_highlighting = warning +resharper_redundant_cast_highlighting = warning +resharper_redundant_catch_clause_highlighting = warning +resharper_redundant_check_before_assignment_highlighting = warning +resharper_redundant_collection_initializer_element_braces_highlighting = hint +resharper_redundant_configure_await_highlighting = suggestion +resharper_redundant_declaration_semicolon_highlighting = hint +resharper_redundant_default_member_initializer_highlighting = warning +resharper_redundant_delegate_creation_highlighting = warning +resharper_redundant_delegate_invoke_highlighting = suggestion +resharper_redundant_dictionary_contains_key_before_adding_highlighting = warning +resharper_redundant_disable_warning_comment_highlighting = warning +resharper_redundant_discard_designation_highlighting = suggestion +resharper_redundant_empty_case_else_highlighting = warning +resharper_redundant_empty_finally_block_highlighting = warning +resharper_redundant_empty_object_creation_argument_list_highlighting = hint +resharper_redundant_empty_object_or_collection_initializer_highlighting = warning +resharper_redundant_empty_switch_section_highlighting = warning +resharper_redundant_enumerable_cast_call_highlighting = warning +resharper_redundant_enum_case_label_for_default_section_highlighting = none +resharper_redundant_explicit_array_creation_highlighting = warning +resharper_redundant_explicit_array_size_highlighting = warning +resharper_redundant_explicit_nullable_creation_highlighting = warning +resharper_redundant_explicit_params_array_creation_highlighting = suggestion +resharper_redundant_explicit_positional_property_declaration_highlighting = warning +resharper_redundant_explicit_tuple_component_name_highlighting = warning +resharper_redundant_extends_list_entry_highlighting = warning +resharper_redundant_fixed_pointer_declaration_highlighting = suggestion +resharper_redundant_if_else_block_highlighting = hint +resharper_redundant_if_statement_then_keyword_highlighting = none +resharper_redundant_immediate_delegate_invocation_highlighting = suggestion +resharper_redundant_inline_assertion_highlighting = suggestion +resharper_redundant_is_before_relational_pattern_highlighting = suggestion +resharper_redundant_iterator_keyword_highlighting = warning +resharper_redundant_jump_statement_highlighting = warning +resharper_redundant_lambda_parameter_type_highlighting = warning +resharper_redundant_lambda_signature_parentheses_highlighting = hint +resharper_redundant_linebreak_highlighting = none +resharper_redundant_logical_conditional_expression_operand_highlighting = warning +resharper_redundant_me_qualifier_highlighting = warning +resharper_redundant_my_base_qualifier_highlighting = warning +resharper_redundant_my_class_qualifier_highlighting = warning +resharper_redundant_name_qualifier_highlighting = warning +resharper_redundant_not_null_constraint_highlighting = warning +resharper_redundant_nullable_annotation_on_reference_type_constraint_highlighting = warning +resharper_redundant_nullable_annotation_on_type_constraint_has_non_nullable_base_type_highlighting = warning +resharper_redundant_nullable_annotation_on_type_constraint_has_non_nullable_type_kind_highlighting = warning +resharper_redundant_nullable_directive_highlighting = warning +resharper_redundant_nullable_flow_attribute_highlighting = warning +resharper_redundant_nullable_type_mark_highlighting = warning +resharper_redundant_nullness_attribute_with_nullable_reference_types_highlighting = warning +resharper_redundant_overflow_checking_context_highlighting = warning +resharper_redundant_overload_global_highlighting = suggestion +resharper_redundant_overload_local_highlighting = suggestion +resharper_redundant_overridden_member_highlighting = warning +resharper_redundant_params_highlighting = warning +resharper_redundant_parentheses_highlighting = none +resharper_redundant_pattern_parentheses_highlighting = hint +resharper_redundant_property_parentheses_highlighting = hint +resharper_redundant_property_pattern_clause_highlighting = suggestion +resharper_redundant_qualifier_highlighting = warning +resharper_redundant_query_order_by_ascending_keyword_highlighting = hint +resharper_redundant_range_bound_highlighting = suggestion +resharper_redundant_readonly_modifier_highlighting = suggestion +resharper_redundant_record_class_keyword_highlighting = warning +resharper_redundant_scoped_parameter_modifier_highlighting = warning +resharper_redundant_setter_value_parameter_declaration_highlighting = hint +resharper_redundant_space_highlighting = none +resharper_redundant_string_format_call_highlighting = warning +resharper_redundant_string_interpolation_highlighting = suggestion +resharper_redundant_string_to_char_array_call_highlighting = warning +resharper_redundant_string_type_highlighting = suggestion +resharper_redundant_suppress_nullable_warning_expression_highlighting = warning +resharper_redundant_ternary_expression_highlighting = warning +resharper_redundant_to_string_call_for_value_type_highlighting = hint +resharper_redundant_to_string_call_highlighting = warning +resharper_redundant_type_arguments_of_method_highlighting = warning +resharper_redundant_type_check_in_pattern_highlighting = warning +resharper_redundant_type_declaration_body_highlighting = warning +resharper_redundant_unsafe_context_highlighting = warning +resharper_redundant_using_directive_global_highlighting = warning +resharper_redundant_using_directive_highlighting = warning +resharper_redundant_verbatim_prefix_highlighting = suggestion +resharper_redundant_verbatim_string_prefix_highlighting = suggestion +resharper_redundant_virtual_modifier_highlighting = warning +resharper_redundant_with_cancellation_highlighting = warning +resharper_redundant_with_expression_highlighting = suggestion +resharper_reference_equals_with_value_type_highlighting = warning +resharper_region_within_type_member_body_highlighting = warning +resharper_region_with_single_element_highlighting = suggestion +resharper_reg_exp_inspections_highlighting = warning +resharper_remove_constructor_invocation_highlighting = none +resharper_remove_redundant_or_statement_false_highlighting = suggestion +resharper_remove_redundant_or_statement_true_highlighting = suggestion +resharper_remove_to_list_1_highlighting = suggestion +resharper_remove_to_list_2_highlighting = suggestion +resharper_replace_async_with_task_return_highlighting = suggestion +resharper_replace_auto_property_with_computed_property_highlighting = hint +resharper_replace_conditional_expression_with_null_coalescing_highlighting = suggestion +resharper_replace_object_pattern_with_var_pattern_highlighting = suggestion +resharper_replace_sequence_equal_with_constant_pattern_highlighting = suggestion +resharper_replace_slice_with_range_indexer_highlighting = hint +resharper_replace_substring_with_range_indexer_highlighting = hint +resharper_replace_with_field_keyword_highlighting = suggestion +resharper_replace_with_first_or_default_1_highlighting = suggestion +resharper_replace_with_first_or_default_2_highlighting = suggestion +resharper_replace_with_first_or_default_3_highlighting = suggestion +resharper_replace_with_first_or_default_4_highlighting = suggestion +resharper_replace_with_last_or_default_1_highlighting = suggestion +resharper_replace_with_last_or_default_2_highlighting = suggestion +resharper_replace_with_last_or_default_3_highlighting = suggestion +resharper_replace_with_last_or_default_4_highlighting = suggestion +resharper_replace_with_of_type_1_highlighting = suggestion +resharper_replace_with_of_type_2_highlighting = suggestion +resharper_replace_with_of_type_3_highlighting = suggestion +resharper_replace_with_of_type_any_1_highlighting = suggestion +resharper_replace_with_of_type_any_2_highlighting = suggestion +resharper_replace_with_of_type_count_1_highlighting = suggestion +resharper_replace_with_of_type_count_2_highlighting = suggestion +resharper_replace_with_of_type_first_1_highlighting = suggestion +resharper_replace_with_of_type_first_2_highlighting = suggestion +resharper_replace_with_of_type_first_or_default_1_highlighting = suggestion +resharper_replace_with_of_type_first_or_default_2_highlighting = suggestion +resharper_replace_with_of_type_last_1_highlighting = suggestion +resharper_replace_with_of_type_last_2_highlighting = suggestion +resharper_replace_with_of_type_last_or_default_1_highlighting = suggestion +resharper_replace_with_of_type_last_or_default_2_highlighting = suggestion +resharper_replace_with_of_type_long_count_highlighting = suggestion +resharper_replace_with_of_type_single_1_highlighting = suggestion +resharper_replace_with_of_type_single_2_highlighting = suggestion +resharper_replace_with_of_type_single_or_default_1_highlighting = suggestion +resharper_replace_with_of_type_single_or_default_2_highlighting = suggestion +resharper_replace_with_of_type_where_highlighting = suggestion +resharper_replace_with_primary_constructor_parameter_highlighting = suggestion +resharper_replace_with_simple_assignment_false_highlighting = suggestion +resharper_replace_with_simple_assignment_true_highlighting = suggestion +resharper_replace_with_single_assignment_false_highlighting = suggestion +resharper_replace_with_single_assignment_true_highlighting = suggestion +resharper_replace_with_single_call_to_any_highlighting = suggestion +resharper_replace_with_single_call_to_count_highlighting = suggestion +resharper_replace_with_single_call_to_first_highlighting = suggestion +resharper_replace_with_single_call_to_first_or_default_highlighting = suggestion +resharper_replace_with_single_call_to_last_highlighting = suggestion +resharper_replace_with_single_call_to_last_or_default_highlighting = suggestion +resharper_replace_with_single_call_to_single_highlighting = suggestion +resharper_replace_with_single_call_to_single_or_default_highlighting = suggestion +resharper_replace_with_single_or_default_1_highlighting = suggestion +resharper_replace_with_single_or_default_2_highlighting = suggestion +resharper_replace_with_single_or_default_3_highlighting = suggestion +resharper_replace_with_single_or_default_4_highlighting = suggestion +resharper_replace_with_string_is_null_or_empty_highlighting = suggestion +resharper_required_base_types_conflict_highlighting = warning +resharper_required_base_types_direct_conflict_highlighting = warning +resharper_required_base_types_is_not_inherited_highlighting = warning +resharper_resource_item_not_resolved_highlighting = error +resharper_resource_not_resolved_highlighting = error +resharper_resx_not_resolved_highlighting = warning +resharper_return_of_task_produced_by_using_variable_highlighting = warning +resharper_return_of_using_variable_highlighting = warning +resharper_return_type_can_be_enumerable_global_highlighting = hint +resharper_return_type_can_be_enumerable_local_highlighting = hint +resharper_return_type_can_be_not_nullable_highlighting = warning +resharper_return_value_of_pure_method_is_not_used_highlighting = warning +resharper_safe_cast_is_used_as_type_check_highlighting = suggestion +resharper_sealed_member_in_sealed_class_highlighting = warning +resharper_separate_control_transfer_statement_highlighting = none +resharper_separate_local_functions_with_jump_statement_highlighting = hint +resharper_service_contract_without_operations_highlighting = warning +resharper_shift_expression_real_shift_count_is_zero_highlighting = warning +resharper_shift_expression_result_equals_zero_highlighting = warning +resharper_shift_expression_right_operand_not_equal_real_count_highlighting = warning +resharper_shift_expression_zero_left_operand_highlighting = warning +resharper_similar_anonymous_type_nearby_highlighting = hint +resharper_simplify_conditional_operator_highlighting = suggestion +resharper_simplify_conditional_ternary_expression_highlighting = suggestion +resharper_simplify_i_if_highlighting = suggestion +resharper_simplify_linq_expression_use_all_highlighting = suggestion +resharper_simplify_linq_expression_use_any_highlighting = suggestion +resharper_simplify_linq_expression_use_min_by_and_max_by_highlighting = suggestion +resharper_simplify_string_interpolation_highlighting = suggestion +resharper_specify_a_culture_in_string_conversion_explicitly_highlighting = warning +resharper_specify_string_comparison_highlighting = hint +resharper_spin_lock_in_readonly_field_highlighting = warning +resharper_stack_alloc_inside_loop_highlighting = warning +resharper_static_member_initializer_referes_to_member_below_highlighting = warning +resharper_static_member_in_generic_type_highlighting = warning +resharper_static_problem_in_text_highlighting = warning +resharper_string_compare_is_culture_specific_1_highlighting = warning +resharper_string_compare_is_culture_specific_2_highlighting = warning +resharper_string_compare_is_culture_specific_3_highlighting = warning +resharper_string_compare_is_culture_specific_4_highlighting = warning +resharper_string_compare_is_culture_specific_5_highlighting = warning +resharper_string_compare_is_culture_specific_6_highlighting = warning +resharper_string_compare_to_is_culture_specific_highlighting = warning +resharper_string_ends_with_is_culture_specific_highlighting = none +resharper_string_index_of_is_culture_specific_1_highlighting = warning +resharper_string_index_of_is_culture_specific_2_highlighting = warning +resharper_string_index_of_is_culture_specific_3_highlighting = warning +resharper_string_last_index_of_is_culture_specific_1_highlighting = warning +resharper_string_last_index_of_is_culture_specific_2_highlighting = warning +resharper_string_last_index_of_is_culture_specific_3_highlighting = warning +resharper_string_literal_as_interpolation_argument_highlighting = suggestion +resharper_string_literal_typo_highlighting = suggestion +resharper_string_starts_with_is_culture_specific_highlighting = none +resharper_structured_message_template_problem_highlighting = warning +resharper_struct_can_be_made_read_only_highlighting = suggestion +resharper_struct_member_can_be_made_read_only_highlighting = none +resharper_suggest_base_type_for_parameter_highlighting = hint +resharper_suggest_base_type_for_parameter_in_constructor_highlighting = hint +resharper_suggest_discard_declaration_var_style_highlighting = hint +resharper_suggest_var_or_type_built_in_types_highlighting = hint +resharper_suggest_var_or_type_deconstruction_declarations_highlighting = hint +resharper_suggest_var_or_type_elsewhere_highlighting = suggestion +resharper_suggest_var_or_type_simple_types_highlighting = hint +csharp_space_before_open_square_brackets = true +resharper_suppress_nullable_warning_expression_as_inverted_is_expression_highlighting = warning +resharper_suspicious_lock_over_synchronization_primitive_highlighting = warning +resharper_suspicious_math_sign_method_highlighting = warning +resharper_suspicious_parameter_name_in_argument_null_exception_highlighting = warning +resharper_suspicious_type_conversion_global_highlighting = warning +resharper_swap_via_deconstruction_highlighting = suggestion +resharper_switch_expression_handles_some_known_enum_values_with_exception_in_default_highlighting = hint +resharper_switch_statement_for_enum_misses_default_section_highlighting = hint +resharper_switch_statement_handles_some_known_enum_values_with_default_highlighting = hint +resharper_switch_statement_missing_some_enum_cases_no_default_highlighting = hint +resharper_symbol_from_not_copied_locally_reference_used_warning_highlighting = warning +resharper_tabs_and_spaces_mismatch_highlighting = warning +resharper_tabs_are_disallowed_highlighting = warning +resharper_tabs_outside_indent_highlighting = none +resharper_tail_recursive_call_highlighting = hint +resharper_thread_static_at_instance_field_highlighting = warning +resharper_thread_static_field_has_initializer_highlighting = warning +resharper_throwing_system_exception_highlighting = suggestion +resharper_throw_exception_in_unexpected_location_highlighting = warning +resharper_throw_from_catch_with_no_inner_exception_highlighting = warning +resharper_too_wide_local_variable_scope_highlighting = suggestion +resharper_try_cast_always_succeeds_highlighting = suggestion +resharper_try_statements_can_be_merged_highlighting = hint +resharper_type_parameter_can_be_variant_highlighting = suggestion +resharper_unassigned_field_global_highlighting = suggestion +resharper_unassigned_field_local_highlighting = warning +resharper_unassigned_get_only_auto_property_highlighting = warning +resharper_unassigned_readonly_field_highlighting = warning +resharper_uncatchable_exception_highlighting = warning +resharper_unnecessary_whitespace_highlighting = warning +resharper_unreachable_switch_arm_due_to_integer_analysis_highlighting = warning +resharper_unreachable_switch_case_due_to_integer_analysis_highlighting = warning +resharper_unsupported_required_base_type_highlighting = warning +resharper_unthrowable_exception_highlighting = warning +resharper_unused_anonymous_method_signature_highlighting = warning +resharper_unused_auto_property_accessor_global_highlighting = warning +resharper_unused_auto_property_accessor_local_highlighting = warning +resharper_unused_import_clause_highlighting = warning +resharper_unused_local_function_highlighting = warning +resharper_unused_local_function_parameter_highlighting = warning +resharper_unused_local_function_return_value_highlighting = warning +resharper_unused_member_global_highlighting = suggestion +resharper_unused_member_hierarchy_global_highlighting = suggestion +resharper_unused_member_hierarchy_local_highlighting = warning +resharper_unused_member_in_super_global_highlighting = suggestion +resharper_unused_member_in_super_local_highlighting = warning +resharper_unused_member_local_highlighting = warning +resharper_unused_method_return_value_global_highlighting = suggestion +resharper_unused_method_return_value_local_highlighting = warning +resharper_unused_nullable_directive_highlighting = warning +resharper_unused_parameter_global_highlighting = suggestion +resharper_unused_parameter_in_partial_method_highlighting = warning +resharper_unused_parameter_local_highlighting = warning +resharper_unused_tuple_component_in_return_value_highlighting = warning +resharper_unused_type_global_highlighting = suggestion +resharper_unused_type_local_highlighting = warning +resharper_unused_type_parameter_highlighting = warning +resharper_unused_variable_highlighting = warning +resharper_useless_binary_operation_highlighting = warning +resharper_useless_comparison_to_integral_constant_highlighting = warning +resharper_use_array_creation_expression_1_highlighting = suggestion +resharper_use_array_creation_expression_2_highlighting = suggestion +resharper_use_array_empty_method_highlighting = suggestion +resharper_use_await_using_highlighting = suggestion +resharper_use_cancellation_token_for_i_async_enumerable_highlighting = suggestion +resharper_use_collection_count_property_highlighting = suggestion +resharper_use_collection_expression_highlighting = suggestion +resharper_use_configure_await_false_for_async_disposable_highlighting = none +resharper_use_configure_await_false_highlighting = suggestion +resharper_use_deconstruction_highlighting = hint +resharper_use_discard_assignment_highlighting = suggestion +resharper_use_empty_for_array_initialization_highlighting = warning +resharper_use_empty_types_field_highlighting = suggestion +resharper_use_event_args_empty_field_highlighting = suggestion +resharper_use_format_specifier_in_format_string_highlighting = suggestion +resharper_use_implicitly_typed_variable_evident_highlighting = hint +resharper_use_implicitly_typed_variable_highlighting = none +resharper_use_implicit_by_val_modifier_highlighting = hint +resharper_use_indexed_property_highlighting = suggestion +resharper_use_index_from_end_expression_highlighting = suggestion +resharper_use_is_operator_1_highlighting = suggestion +resharper_use_is_operator_2_highlighting = suggestion +resharper_use_method_any_0_highlighting = suggestion +resharper_use_method_any_1_highlighting = suggestion +resharper_use_method_any_2_highlighting = suggestion +resharper_use_method_any_3_highlighting = suggestion +resharper_use_method_any_4_highlighting = suggestion +resharper_use_method_is_instance_of_type_highlighting = suggestion +resharper_use_nameof_expression_for_part_of_the_string_highlighting = none +resharper_use_nameof_expression_highlighting = suggestion +resharper_use_nameof_for_dependency_property_highlighting = suggestion +resharper_use_name_of_instead_of_type_of_highlighting = suggestion +resharper_use_negated_pattern_in_is_expression_highlighting = hint +resharper_use_negated_pattern_matching_highlighting = hint +resharper_use_nullable_annotation_instead_of_attribute_highlighting = suggestion +resharper_use_nullable_attributes_supported_by_compiler_highlighting = suggestion +resharper_use_nullable_reference_types_annotation_syntax_highlighting = warning +resharper_use_null_propagation_highlighting = hint +resharper_use_object_or_collection_initializer_highlighting = suggestion +resharper_use_pattern_matching_highlighting = suggestion +resharper_use_positional_deconstruction_pattern_highlighting = none +resharper_use_raw_string_highlighting = hint +resharper_use_string_interpolation_highlighting = suggestion +resharper_use_string_interpolation_when_possible_highlighting = hint +resharper_use_switch_case_pattern_variable_highlighting = suggestion +resharper_use_symbol_alias_highlighting = hint +resharper_use_target_typed_collection_expression_highlighting = suggestion +resharper_use_throw_if_null_method_highlighting = warning +resharper_use_unsigned_right_shift_operator_highlighting = suggestion +resharper_use_verbatim_string_highlighting = hint +resharper_use_with_expression_to_copy_anonymous_object_highlighting = suggestion +resharper_use_with_expression_to_copy_record_highlighting = suggestion +resharper_use_with_expression_to_copy_struct_highlighting = suggestion +resharper_use_with_expression_to_copy_tuple_highlighting = suggestion +resharper_using_statement_resource_initialization_expression_highlighting = hint +resharper_using_statement_resource_initialization_highlighting = warning +resharper_value_parameter_not_used_highlighting = warning +resharper_value_range_attribute_violation_highlighting = warning +resharper_variable_can_be_not_nullable_highlighting = warning +resharper_variable_hides_outer_variable_highlighting = warning +resharper_vb_check_for_reference_equality_instead_1_highlighting = suggestion +resharper_vb_check_for_reference_equality_instead_2_highlighting = suggestion +resharper_vb_possible_mistaken_argument_highlighting = warning +resharper_vb_possible_mistaken_call_to_get_type_1_highlighting = warning +resharper_vb_possible_mistaken_call_to_get_type_2_highlighting = warning +resharper_vb_remove_to_list_1_highlighting = suggestion +resharper_vb_remove_to_list_2_highlighting = suggestion +resharper_vb_replace_with_first_or_default_highlighting = suggestion +resharper_vb_replace_with_last_or_default_highlighting = suggestion +resharper_vb_replace_with_of_type_1_highlighting = suggestion +resharper_vb_replace_with_of_type_2_highlighting = suggestion +resharper_vb_replace_with_of_type_any_1_highlighting = suggestion +resharper_vb_replace_with_of_type_any_2_highlighting = suggestion +resharper_vb_replace_with_of_type_count_1_highlighting = suggestion +resharper_vb_replace_with_of_type_count_2_highlighting = suggestion +resharper_vb_replace_with_of_type_first_1_highlighting = suggestion +resharper_vb_replace_with_of_type_first_2_highlighting = suggestion +resharper_vb_replace_with_of_type_first_or_default_1_highlighting = suggestion +resharper_vb_replace_with_of_type_first_or_default_2_highlighting = suggestion +resharper_vb_replace_with_of_type_last_1_highlighting = suggestion +resharper_vb_replace_with_of_type_last_2_highlighting = suggestion +resharper_vb_replace_with_of_type_last_or_default_1_highlighting = suggestion +resharper_vb_replace_with_of_type_last_or_default_2_highlighting = suggestion +resharper_vb_replace_with_of_type_single_1_highlighting = suggestion +resharper_vb_replace_with_of_type_single_2_highlighting = suggestion +resharper_vb_replace_with_of_type_single_or_default_1_highlighting = suggestion +resharper_vb_replace_with_of_type_single_or_default_2_highlighting = suggestion +resharper_vb_replace_with_of_type_where_highlighting = suggestion +resharper_vb_replace_with_single_assignment_1_highlighting = suggestion +resharper_vb_replace_with_single_assignment_2_highlighting = suggestion +resharper_vb_replace_with_single_call_to_any_highlighting = suggestion +resharper_vb_replace_with_single_call_to_count_highlighting = suggestion +resharper_vb_replace_with_single_call_to_first_highlighting = suggestion +resharper_vb_replace_with_single_call_to_first_or_default_highlighting = suggestion +resharper_vb_replace_with_single_call_to_last_highlighting = suggestion +resharper_vb_replace_with_single_call_to_last_or_default_highlighting = suggestion +resharper_vb_replace_with_single_call_to_single_highlighting = suggestion +resharper_vb_replace_with_single_call_to_single_or_default_highlighting = suggestion +resharper_vb_replace_with_single_or_default_highlighting = suggestion +resharper_vb_simplify_linq_expression_10_highlighting = hint +resharper_vb_simplify_linq_expression_1_highlighting = suggestion +resharper_vb_simplify_linq_expression_2_highlighting = suggestion +resharper_vb_simplify_linq_expression_3_highlighting = suggestion +resharper_vb_simplify_linq_expression_4_highlighting = suggestion +resharper_vb_simplify_linq_expression_5_highlighting = suggestion +resharper_vb_simplify_linq_expression_6_highlighting = suggestion +resharper_vb_simplify_linq_expression_7_highlighting = hint +resharper_vb_simplify_linq_expression_8_highlighting = hint +resharper_vb_simplify_linq_expression_9_highlighting = hint +resharper_vb_string_compare_is_culture_specific_1_highlighting = warning +resharper_vb_string_compare_is_culture_specific_2_highlighting = warning +resharper_vb_string_compare_is_culture_specific_3_highlighting = warning +resharper_vb_string_compare_is_culture_specific_4_highlighting = warning +resharper_vb_string_compare_is_culture_specific_5_highlighting = warning +resharper_vb_string_compare_is_culture_specific_6_highlighting = warning +resharper_vb_string_compare_to_is_culture_specific_highlighting = warning +resharper_vb_string_ends_with_is_culture_specific_highlighting = none +resharper_vb_string_index_of_is_culture_specific_1_highlighting = warning +resharper_vb_string_index_of_is_culture_specific_2_highlighting = warning +resharper_vb_string_index_of_is_culture_specific_3_highlighting = warning +resharper_vb_string_last_index_of_is_culture_specific_1_highlighting = warning +resharper_vb_string_last_index_of_is_culture_specific_2_highlighting = warning +resharper_vb_string_last_index_of_is_culture_specific_3_highlighting = warning +resharper_vb_string_starts_with_is_culture_specific_highlighting = none +resharper_vb_unreachable_code_highlighting = warning +resharper_vb_use_array_creation_expression_1_highlighting = suggestion +resharper_vb_use_array_creation_expression_2_highlighting = suggestion +resharper_vb_use_first_instead_highlighting = warning +resharper_vb_use_method_any_1_highlighting = suggestion +resharper_vb_use_method_any_2_highlighting = suggestion +resharper_vb_use_method_any_3_highlighting = suggestion +resharper_vb_use_method_any_4_highlighting = suggestion +resharper_vb_use_method_any_5_highlighting = suggestion +resharper_vb_use_method_is_instance_of_type_highlighting = suggestion +resharper_vb_use_type_of_is_operator_1_highlighting = suggestion +resharper_vb_use_type_of_is_operator_2_highlighting = suggestion +resharper_virtual_member_call_in_constructor_highlighting = warning +resharper_virtual_member_never_overridden_global_highlighting = suggestion +resharper_virtual_member_never_overridden_local_highlighting = suggestion +resharper_void_method_with_must_dispose_resource_attribute_highlighting = warning +resharper_void_method_with_must_use_return_value_attribute_highlighting = warning +resharper_vulnerable_api_highlighting = warning +resharper_with_expression_instead_of_initializer_highlighting = suggestion +resharper_with_expression_modifies_all_members_highlighting = warning +resharper_wrong_indent_size_highlighting = none +resharper_xaml_assign_null_to_not_null_attribute_highlighting = warning +resharper_xaml_avalonia_wrong_binding_mode_for_stream_binding_operator_highlighting = warning +resharper_xaml_binding_without_context_not_resolved_highlighting = hint +resharper_xaml_binding_without_mode_highlighting = warning +resharper_xaml_binding_with_context_not_resolved_highlighting = warning +resharper_xaml_compiled_binding_missing_data_type_error_highlighting_highlighting = error +resharper_xaml_constructor_warning_highlighting = warning +resharper_xaml_decimal_parsing_is_culture_dependent_highlighting = warning +resharper_xaml_dependency_property_resolve_error_highlighting = warning +resharper_xaml_duplicate_style_setter_highlighting = warning +resharper_xaml_dynamic_resource_error_highlighting = error +resharper_xaml_element_name_reference_not_resolved_highlighting = error +resharper_xaml_empty_grid_length_definition_highlighting = error +resharper_xaml_field_modifier_requires_name_attribute_highlighting = warning +resharper_xaml_grid_definitions_can_be_converted_to_attribute_highlighting = hint +resharper_xaml_ignored_path_highlighting_highlighting = none +resharper_xaml_index_out_of_grid_definition_highlighting = warning +resharper_xaml_invalid_member_type_highlighting = error +resharper_xaml_invalid_resource_target_type_highlighting = error +resharper_xaml_invalid_resource_type_highlighting = error +resharper_xaml_invalid_type_highlighting = error +resharper_xaml_language_level_highlighting = error +resharper_xaml_mapped_path_highlighting_highlighting = hint +resharper_xaml_method_arguments_will_be_ignored_highlighting = warning +resharper_xaml_missing_grid_index_highlighting = warning +resharper_xaml_overloads_collision_highlighting = warning +resharper_xaml_parent_is_out_of_current_component_tree_highlighting = warning +resharper_xaml_path_error_highlighting = warning +resharper_xaml_possible_null_reference_exception_highlighting = suggestion +resharper_xaml_redundant_attached_property_highlighting = warning +resharper_xaml_redundant_binding_mode_attribute_highlighting = warning +resharper_xaml_redundant_collection_property_highlighting = warning +resharper_xaml_redundant_freeze_attribute_highlighting = warning +resharper_xaml_redundant_grid_definitions_highlighting = warning +resharper_xaml_redundant_grid_span_highlighting = warning +resharper_xaml_redundant_modifiers_attribute_highlighting = warning +resharper_xaml_redundant_namespace_alias_highlighting = warning +resharper_xaml_redundant_name_attribute_highlighting = warning +resharper_xaml_redundant_property_type_qualifier_highlighting = warning +resharper_xaml_redundant_resource_highlighting = warning +resharper_xaml_redundant_styled_value_highlighting = warning +resharper_xaml_redundant_update_source_trigger_attribute_highlighting = warning +resharper_xaml_redundant_xamarin_forms_class_declaration_highlighting = warning +resharper_xaml_resource_file_path_case_mismatch_highlighting = warning +resharper_xaml_routed_event_resolve_error_highlighting = warning +resharper_xaml_static_resource_not_resolved_highlighting = warning +resharper_xaml_style_class_not_found_highlighting = warning +resharper_xaml_style_invalid_target_type_highlighting = error +resharper_xaml_unexpected_element_highlighting = error +resharper_xaml_unexpected_text_token_highlighting = error +resharper_xaml_xaml_duplicate_device_family_type_view_highlighting_highlighting = error +resharper_xaml_xaml_mismatched_device_family_view_clr_name_highlighting_highlighting = warning +resharper_xaml_xaml_relative_source_default_mode_warning_highlighting_highlighting = warning +resharper_xaml_xaml_unknown_device_family_type_highlighting_highlighting = warning +resharper_xaml_xaml_xamarin_forms_data_type_and_binding_context_type_mismatched_highlighting_highlighting = warning +resharper_xaml_x_key_attribute_disallowed_highlighting = error +resharper_xunit_xunit_test_with_console_output_highlighting = warning +resharper_yield_return_within_lock_highlighting = warning +resharper_zero_index_from_end_highlighting = warning + +# Standard properties +end_of_line = native [*.{cs,vb}] dotnet_style_operator_placement_when_wrapping = beginning_of_line -tab_width = 8 -indent_size = 8 end_of_line = crlf dotnet_style_coalesce_expression = true:suggestion dotnet_style_null_propagation = true:suggestion @@ -155,3 +1864,23 @@ dotnet_style_qualification_for_field = false:silent dotnet_style_qualification_for_property = false:silent dotnet_style_qualification_for_method = false:silent dotnet_style_qualification_for_event = false:silent + +[*.{cshtml,htm,html,razor}] +indent_style = tab +indent_size = tab +tab_width = 4 + +[*.{asax,ascx,aspx,axaml,cs,master,paml,skin,vb,xaml,xamlx,xoml}] +indent_style = space +indent_size = 4 +tab_width = 4 + +[*.{appxmanifest,axml,build,config,csproj,dbml,discomap,dtd,jsproj,lsproj,njsproj,nuspec,proj,props,resw,resx,StyleCop,targets,tasks,vbproj,xml,xsd}] +indent_style = space +indent_size = 2 +tab_width = 2 + +[*.{appxmanifest,axaml,axml,build,config,cs,csproj,dbml,discomap,dtd,jsproj,lsproj,njsproj,nuspec,paml,proj,props,resw,resx,StyleCop,targets,tasks,vb,vbproj,xaml,xamlx,xml,xoml,xsd}] +indent_style = space +indent_size = 4 +tab_width = 4 diff --git a/Terminal.Gui/Text/TextFormatter.cs b/Terminal.Gui/Text/TextFormatter.cs index a15d43399..5c8859156 100644 --- a/Terminal.Gui/Text/TextFormatter.cs +++ b/Terminal.Gui/Text/TextFormatter.cs @@ -1,1664 +1,2055 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; +namespace Terminal.Gui; -namespace Terminal.Gui { - /// - /// Text alignment enumeration, controls how text is displayed. - /// - public enum TextAlignment { - /// - /// The text will be left-aligned. - /// - Left, - /// - /// The text will be right-aligned. - /// - Right, - /// - /// The text will be centered horizontally. - /// - Centered, - /// - /// The text will be justified (spaces will be added to existing spaces such that - /// the text fills the container horizontally). - /// - Justified - } +/// Text alignment enumeration, controls how text is displayed. +public enum TextAlignment +{ + /// The text will be left-aligned. + Left, - /// - /// Vertical text alignment enumeration, controls how text is displayed. - /// - public enum VerticalTextAlignment { - /// - /// The text will be top-aligned. - /// - Top, - /// - /// The text will be bottom-aligned. - /// - Bottom, - /// - /// The text will centered vertically. - /// - Middle, - /// - /// The text will be justified (spaces will be added to existing spaces such that - /// the text fills the container vertically). - /// - Justified - } + /// The text will be right-aligned. + Right, - /// - /// Text direction enumeration, controls how text is displayed. - /// - /// - /// TextDirection [H] = Horizontal [V] = Vertical - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - ///
TextDirectionDescription
LeftRight_TopBottom [H]Normal
TopBottom_LeftRight [V]Normal
RightLeft_TopBottom [H]Invert Text
TopBottom_RightLeft [V]Invert Lines
LeftRight_BottomTop [H]Invert Lines
BottomTop_LeftRight [V]Invert Text
RightLeft_BottomTop [H]Invert Text + Invert Lines
BottomTop_RightLeft [V]Invert Text + Invert Lines
- ///
- public enum TextDirection { - /// - /// Normal horizontal direction. - /// HELLO
WORLD
- ///
- LeftRight_TopBottom, - /// - /// Normal vertical direction. - /// H W
E O
L R
L L
O D
- ///
- TopBottom_LeftRight, - /// - /// This is a horizontal direction.
RTL - /// OLLEH
DLROW
- ///
- RightLeft_TopBottom, - /// - /// This is a vertical direction. - /// W H
O E
R L
L L
D O
- ///
- TopBottom_RightLeft, - /// - /// This is a horizontal direction. - /// WORLD
HELLO
- ///
- LeftRight_BottomTop, - /// - /// This is a vertical direction. - /// O D
L L
L R
E O
H W
- ///
- BottomTop_LeftRight, - /// - /// This is a horizontal direction. - /// DLROW
OLLEH
- ///
- RightLeft_BottomTop, - /// - /// This is a vertical direction. - /// D O
L L
R L
O E
W H
- ///
- BottomTop_RightLeft - } + /// The text will be centered horizontally. + Centered, - /// - /// Provides text formatting. Supports s, horizontal alignment, vertical alignment, multiple lines, and word-based line wrap. - /// - public class TextFormatter { - - #region Static Members - - static string StripCRLF (string str, bool keepNewLine = false) - { - var runes = str.ToRuneList (); - for (int i = 0; i < runes.Count; i++) { - switch ((char)runes [i].Value) { - case '\n': - if (!keepNewLine) { - runes.RemoveAt (i); - } - break; - - case '\r': - if ((i + 1) < runes.Count && runes [i + 1].Value == '\n') { - runes.RemoveAt (i); - if (!keepNewLine) { - runes.RemoveAt (i); - } - i++; - } else { - if (!keepNewLine) { - runes.RemoveAt (i); - } - } - break; - } - } - return StringExtensions.ToString (runes); - } - static string ReplaceCRLFWithSpace (string str) - { - var runes = str.ToRuneList (); - for (int i = 0; i < runes.Count; i++) { - switch (runes [i].Value) { - case '\n': - runes [i] = (Rune)' '; - break; - - case '\r': - if ((i + 1) < runes.Count && runes [i + 1].Value == '\n') { - runes [i] = (Rune)' '; - runes.RemoveAt (i + 1); - i++; - } else { - runes [i] = (Rune)' '; - } - break; - } - } - return StringExtensions.ToString (runes); - } - - static string ReplaceTABWithSpaces (string str, int tabWidth) - { - if (tabWidth == 0) { - return str.Replace ("\t", ""); - } - - return str.Replace ("\t", new string (' ', tabWidth)); - } - - /// - /// Splits all newlines in the into a list - /// and supports both CRLF and LF, preserving the ending newline. - /// - /// The text. - /// A list of text without the newline characters. - public static List SplitNewLine (string text) - { - var runes = text.ToRuneList (); - var lines = new List (); - var start = 0; - var end = 0; - - for (int i = 0; i < runes.Count; i++) { - end = i; - switch (runes [i].Value) { - case '\n': - lines.Add (StringExtensions.ToString (runes.GetRange (start, end - start))); - i++; - start = i; - break; - - case '\r': - if ((i + 1) < runes.Count && runes [i + 1].Value == '\n') { - lines.Add (StringExtensions.ToString (runes.GetRange (start, end - start))); - i += 2; - start = i; - } else { - lines.Add (StringExtensions.ToString (runes.GetRange (start, end - start))); - i++; - start = i; - } - break; - } - } - if (runes.Count > 0 && lines.Count == 0) { - lines.Add (StringExtensions.ToString (runes)); - } else if (runes.Count > 0 && start < runes.Count) { - lines.Add (StringExtensions.ToString (runes.GetRange (start, runes.Count - start))); - } else { - lines.Add (""); - } - return lines; - } - - /// - /// Adds trailing whitespace or truncates - /// so that it fits exactly console units. - /// Note that some unicode characters take 2+ columns - /// - /// - /// - /// - public static string ClipOrPad (string text, int width) - { - if (string.IsNullOrEmpty (text)) - return text; - - // if value is not wide enough - if (text.EnumerateRunes ().Sum (c => c.GetColumns ()) < width) { - - // pad it out with spaces to the given alignment - int toPad = width - (text.EnumerateRunes ().Sum (c => c.GetColumns ())); - - return text + new string (' ', toPad); - } - - // value is too wide - return new string (text.TakeWhile (c => (width -= ((Rune)c).GetColumns ()) >= 0).ToArray ()); - } - - /// - /// Formats the provided text to fit within the width provided using word wrapping. - /// - /// The text to word wrap - /// The number of columns to constrain the text to - /// If trailing spaces at the end of wrapped lines will be preserved. - /// If , trailing spaces at the end of wrapped lines will be trimmed. - /// The number of columns used for a tab. - /// The text direction. - /// A list of word wrapped lines. - /// - /// - /// This method does not do any justification. - /// - /// - /// This method strips Newline ('\n' and '\r\n') sequences before processing. - /// - /// - /// If is at most one space will be preserved at the end of the last line. - /// - /// - public static List WordWrapText (string text, int width, bool preserveTrailingSpaces = false, int tabWidth = 0, - TextDirection textDirection = TextDirection.LeftRight_TopBottom) - { - if (width < 0) { - throw new ArgumentOutOfRangeException ("Width cannot be negative."); - } - - int start = 0, end; - var lines = new List (); - - if (string.IsNullOrEmpty (text)) { - return lines; - } - - var runes = StripCRLF (text).ToRuneList (); - if (preserveTrailingSpaces) { - while ((end = start) < runes.Count) { - end = GetNextWhiteSpace (start, width, out bool incomplete); - if (end == 0 && incomplete) { - start = text.GetRuneCount (); - break; - } - lines.Add (StringExtensions.ToString (runes.GetRange (start, end - start))); - start = end; - if (incomplete) { - start = text.GetRuneCount (); - break; - } - } - } else { - if (IsHorizontalDirection (textDirection)) { - //if (GetLengthThatFits (runes.GetRange (start, runes.Count - start), width) > 0) { - // // while there's still runes left and end is not past end... - // while (start < runes.Count && - // (end = start + Math.Max (GetLengthThatFits (runes.GetRange (start, runes.Count - start), width) - 1, 0)) < runes.Count) { - // // end now points to start + LengthThatFits - // // Walk back over trailing spaces - // while (runes [end] == ' ' && end > start) { - // end--; - // } - // // end now points to start + LengthThatFits - any trailing spaces; start saving new line - // var line = runes.GetRange (start, end - start + 1); - - // if (end == start && width > 1) { - // // it was all trailing spaces; now walk forward to next non-space - // do { - // start++; - // } while (start < runes.Count && runes [start] == ' '); - - // // start now points to first non-space we haven't seen yet or we're done - // if (start < runes.Count) { - // // we're not done. we have remaining = width - line.Count columns left; - // var remaining = width - line.Count; - // if (remaining > 1) { - // // add a space for all the spaces we walked over - // line.Add (' '); - // } - // var count = GetLengthThatFits (runes.GetRange (start, runes.Count - start), width - line.Count); - - // // [start..count] now has rest of line - // line.AddRange (runes.GetRange (start, count)); - // start += count; - // } - // } else { - // start += line.Count; - // } - - // //// if the previous line was just a ' ' and the new line is just a ' ' - // //// don't add new line - // //if (line [0] == ' ' && (lines.Count > 0 && lines [lines.Count - 1] [0] == ' ')) { - // //} else { - // //} - // lines.Add (string.Make (line)); - - // // move forward to next non-space - // while (width > 1 && start < runes.Count && runes [start] == ' ') { - // start++; - // } - // } - //} - - while ((end = start + GetLengthThatFits (runes.GetRange (start, runes.Count - start), width, tabWidth)) < runes.Count) { - while (runes [end].Value != ' ' && end > start) - end--; - if (end == start) - end = start + GetLengthThatFits (runes.GetRange (end, runes.Count - end), width, tabWidth); - var str = StringExtensions.ToString (runes.GetRange (start, end - start)); - if (end > start && GetRuneWidth (str, tabWidth) <= width) { - lines.Add (str); - start = end; - if (runes [end].Value == ' ') { - start++; - } - } else { - end++; - start = end; - } - } - - } else { - while ((end = start + width) < runes.Count) { - while (runes [end].Value != ' ' && end > start) { - end--; - } - if (end == start) { - end = start + width; - } - var zeroLength = 0; - for (int i = end; i < runes.Count - start; i++) { - var r = runes [i]; - if (r.GetColumns () == 0) { - zeroLength++; - } else { - break; - } - } - lines.Add (StringExtensions.ToString (runes.GetRange (start, end - start + zeroLength))); - end += zeroLength; - start = end; - if (runes [end].Value == ' ') { - start++; - } - } - } - } - - int GetNextWhiteSpace (int from, int cWidth, out bool incomplete, int cLength = 0) - { - var lastFrom = from; - var to = from; - var length = cLength; - incomplete = false; - - while (length < cWidth && to < runes.Count) { - var rune = runes [to]; - if (IsHorizontalDirection (textDirection)) { - length += rune.GetColumns (); - } else { - length++; - } - if (length > cWidth) { - if (to >= runes.Count || (length > 1 && cWidth <= 1)) { - incomplete = true; - } - return to; - } - if (rune.Value == ' ') { - if (length == cWidth) { - return to + 1; - } else if (length > cWidth) { - return to; - } else { - return GetNextWhiteSpace (to + 1, cWidth, out incomplete, length); - } - } else if (rune.Value == '\t') { - length += tabWidth + 1; - if (length == tabWidth && tabWidth > cWidth) { - return to + 1; - } else if (length > cWidth && tabWidth > cWidth) { - return to; - } else { - return GetNextWhiteSpace (to + 1, cWidth, out incomplete, length); - } - } - to++; - } - if (cLength > 0 && to < runes.Count && runes [to].Value != ' ' && runes [to].Value != '\t') { - return from; - } else if (cLength > 0 && to < runes.Count && (runes [to].Value == ' ' || runes [to].Value == '\t')) { - return lastFrom; - } else { - return to; - } - } - - if (start < text.GetRuneCount ()) { - var str = ReplaceTABWithSpaces (StringExtensions.ToString (runes.GetRange (start, runes.Count - start)), tabWidth); - if (IsVerticalDirection (textDirection) || preserveTrailingSpaces || (!preserveTrailingSpaces && str.GetColumns () <= width)) { - lines.Add (str); - } - } - - return lines; - } - - /// - /// Justifies text within a specified width. - /// - /// The text to justify. - /// The number of columns to clip the text to. Text longer than will be clipped. - /// Alignment. - /// The text direction. - /// The number of columns used for a tab. - /// Justified and clipped text. - public static string ClipAndJustify (string text, int width, TextAlignment talign, TextDirection textDirection = TextDirection.LeftRight_TopBottom, int tabWidth = 0) - { - return ClipAndJustify (text, width, talign == TextAlignment.Justified, textDirection, tabWidth); - } - - /// - /// Justifies text within a specified width. - /// - /// The text to justify. - /// The number of columns to clip the text to. Text longer than will be clipped. - /// Justify. - /// The text direction. - /// The number of columns used for a tab. - /// Justified and clipped text. - public static string ClipAndJustify (string text, int width, bool justify, TextDirection textDirection = TextDirection.LeftRight_TopBottom, int tabWidth = 0) - { - if (width < 0) { - throw new ArgumentOutOfRangeException ("Width cannot be negative."); - } - if (string.IsNullOrEmpty (text)) { - return text; - } - - text = ReplaceTABWithSpaces (text, tabWidth); - var runes = text.ToRuneList (); - int slen = runes.Count; - if (slen > width) { - if (IsHorizontalDirection (textDirection)) { - return StringExtensions.ToString (runes.GetRange (0, GetLengthThatFits (text, width, tabWidth))); - } else { - var zeroLength = runes.Sum (r => r.GetColumns () == 0 ? 1 : 0); - return StringExtensions.ToString (runes.GetRange (0, width + zeroLength)); - } - } else { - if (justify) { - return Justify (text, width, ' ', textDirection, tabWidth); - } else if (IsHorizontalDirection (textDirection) && GetRuneWidth (text, tabWidth) > width) { - return StringExtensions.ToString (runes.GetRange (0, GetLengthThatFits (text, width, tabWidth))); - } - return text; - } - } - - /// - /// Justifies the text to fill the width provided. Space will be added between words (demarked by spaces and tabs) to - /// make the text just fit width. Spaces will not be added to the ends. - /// - /// - /// - /// Character to replace whitespace and pad with. For debugging purposes. - /// The text direction. - /// The number of columns used for a tab. - /// The justified text. - public static string Justify (string text, int width, char spaceChar = ' ', TextDirection textDirection = TextDirection.LeftRight_TopBottom, int tabWidth = 0) - { - if (width < 0) { - throw new ArgumentOutOfRangeException ("Width cannot be negative."); - } - if (string.IsNullOrEmpty (text)) { - return text; - } - - text = ReplaceTABWithSpaces (text, tabWidth); - var words = text.Split (' '); - int textCount; - if (IsHorizontalDirection (textDirection)) { - textCount = words.Sum (arg => GetRuneWidth (arg, tabWidth)); - } else { - textCount = words.Sum (arg => arg.GetRuneCount ()); - } - var spaces = words.Length > 1 ? (width - textCount) / (words.Length - 1) : 0; - var extras = words.Length > 1 ? (width - textCount) % (words.Length - 1) : 0; - - var s = new System.Text.StringBuilder (); - for (int w = 0; w < words.Length; w++) { - var x = words [w]; - s.Append (x); - if (w + 1 < words.Length) - for (int i = 0; i < spaces; i++) - s.Append (spaceChar); - if (extras > 0) { - for (int i = 0; i < 1; i++) - s.Append (spaceChar); - extras--; - } - if (w + 1 == words.Length - 1) { - for (int i = 0; i < extras; i++) - s.Append (spaceChar); - } - } - return s.ToString (); - } - - //static char [] whitespace = new char [] { ' ', '\t' }; - - /// - /// Reformats text into lines, applying text alignment and optionally wrapping text to new lines on word boundaries. - /// - /// - /// The number of columns to constrain the text to for word wrapping and clipping. - /// Specifies how the text will be aligned horizontally. - /// If , the text will be wrapped to new lines no longer than . - /// If , forces text to fit a single line. Line breaks are converted to spaces. The text will be clipped to . - /// If trailing spaces at the end of wrapped lines will be preserved. - /// If , trailing spaces at the end of wrapped lines will be trimmed. - /// The number of columns used for a tab. - /// The text direction. - /// If new lines are allowed. - /// A list of word wrapped lines. - /// - /// - /// An empty string will result in one empty line. - /// - /// - /// If is 0, a single, empty line will be returned. - /// - /// - /// If is int.MaxValue, the text will be formatted to the maximum width possible. - /// - /// - public static List Format (string text, int width, TextAlignment talign, bool wordWrap, bool preserveTrailingSpaces = false, int tabWidth = 0, TextDirection textDirection = TextDirection.LeftRight_TopBottom, bool multiLine = false) - { - return Format (text, width, talign == TextAlignment.Justified, wordWrap, preserveTrailingSpaces, tabWidth, textDirection, multiLine); - } - - /// - /// Reformats text into lines, applying text alignment and optionally wrapping text to new lines on word boundaries. - /// - /// - /// The number of columns to constrain the text to for word wrapping and clipping. - /// Specifies whether the text should be justified. - /// If , the text will be wrapped to new lines no longer than . - /// If , forces text to fit a single line. Line breaks are converted to spaces. The text will be clipped to . - /// If trailing spaces at the end of wrapped lines will be preserved. - /// If , trailing spaces at the end of wrapped lines will be trimmed. - /// The number of columns used for a tab. - /// The text direction. - /// If new lines are allowed. - /// A list of word wrapped lines. - /// - /// - /// An empty string will result in one empty line. - /// - /// - /// If is 0, a single, empty line will be returned. - /// - /// - /// If is int.MaxValue, the text will be formatted to the maximum width possible. - /// - /// - public static List Format (string text, int width, bool justify, bool wordWrap, - bool preserveTrailingSpaces = false, int tabWidth = 0, TextDirection textDirection = TextDirection.LeftRight_TopBottom, bool multiLine = false) - { - if (width < 0) { - throw new ArgumentOutOfRangeException ("width cannot be negative"); - } - List lineResult = new List (); - - if (string.IsNullOrEmpty (text) || width == 0) { - lineResult.Add (string.Empty); - return lineResult; - } - - if (!wordWrap) { - text = ReplaceTABWithSpaces (text, tabWidth); - if (multiLine) { - string [] lines = null; - if (text.Contains ("\r\n")) { - lines = text.Split ("\r\n"); - } else if (text.Contains ('\n')) { - lines = text.Split ('\n'); - } - if (lines == null) { - lines = new [] { text }; - } - foreach (var line in lines) { - lineResult.Add (ClipAndJustify (line, width, justify, textDirection, tabWidth)); - } - return lineResult; - } else { - text = ReplaceCRLFWithSpace (text); - lineResult.Add (ClipAndJustify (text, width, justify, textDirection, tabWidth)); - return lineResult; - } - } - - var runes = StripCRLF (text, true).ToRuneList (); - int runeCount = runes.Count; - int lp = 0; - for (int i = 0; i < runeCount; i++) { - Rune c = runes [i]; - if (c.Value == '\n') { - var wrappedLines = WordWrapText (StringExtensions.ToString (runes.GetRange (lp, i - lp)), width, preserveTrailingSpaces, tabWidth, textDirection); - foreach (var line in wrappedLines) { - lineResult.Add (ClipAndJustify (line, width, justify, textDirection, tabWidth)); - } - if (wrappedLines.Count == 0) { - lineResult.Add (string.Empty); - } - lp = i + 1; - } - } - foreach (var line in WordWrapText (StringExtensions.ToString (runes.GetRange (lp, runeCount - lp)), width, preserveTrailingSpaces, tabWidth, textDirection)) { - lineResult.Add (ClipAndJustify (line, width, justify, textDirection, tabWidth)); - } - - return lineResult; - } - - /// - /// Computes the number of lines needed to render the specified text given the width. - /// - /// Number of lines. - /// Text, may contain newlines. - /// The minimum width for the text. - public static int MaxLines (string text, int width) - { - var result = TextFormatter.Format (text, width, false, true); - return result.Count; - } - - /// - /// Computes the maximum width needed to render the text (single line or multiple lines, word wrapped) given - /// a number of columns to constrain the text to. - /// - /// Width of the longest line after formatting the text constrained by . - /// Text, may contain newlines. - /// The number of columns to constrain the text to for formatting. - /// The number of columns used for a tab. - public static int MaxWidth (string text, int maxColumns, int tabWidth = 0) - { - var result = TextFormatter.Format (text: text, width: maxColumns, justify: false, wordWrap: true); - var max = 0; - result.ForEach (s => { - var m = 0; - s.ToRuneList ().ForEach (r => m += GetRuneWidth (r, tabWidth)); - if (m > max) { - max = m; - } - }); - return max; - } - - /// - /// Returns the width of the widest line in the text, accounting for wide-glyphs (uses ). - /// if it contains newlines. - /// - /// Text, may contain newlines. - /// The number of columns used for a tab. - /// The length of the longest line. - public static int MaxWidthLine (string text, int tabWidth = 0) - { - var result = TextFormatter.SplitNewLine (text); - return result.Max (x => GetRuneWidth (x, tabWidth)); - } - - /// - /// Gets the maximum characters width from the list based on the - /// and the . - /// - /// The lines. - /// The start index. - /// The length. - /// The number of columns used for a tab. - /// The maximum characters width. - public static int GetSumMaxCharWidth (List lines, int startIndex = -1, int length = -1, int tabWidth = 0) - { - var max = 0; - for (int i = (startIndex == -1 ? 0 : startIndex); i < (length == -1 ? lines.Count : startIndex + length); i++) { - var runes = lines [i]; - if (runes.Length > 0) - max += runes.EnumerateRunes ().Max (r => GetRuneWidth (r, tabWidth)); - } - return max; - } - - /// - /// Gets the maximum characters width from the text based on the - /// and the . - /// - /// The text. - /// The start index. - /// The length. - /// The number of columns used for a tab. - /// The maximum characters width. - public static int GetSumMaxCharWidth (string text, int startIndex = -1, int length = -1, int tabWidth = 0) - { - var max = 0; - var runes = text.ToRunes (); - for (int i = (startIndex == -1 ? 0 : startIndex); i < (length == -1 ? runes.Length : startIndex + length); i++) { - max += GetRuneWidth (runes [i], tabWidth); - } - return max; - } - - /// - /// Gets the number of the Runes in a that will fit in . - /// - /// The text. - /// The width. - /// The number of columns used for a tab. - /// The index of the text that fit the width. - public static int GetLengthThatFits (string text, int columns, int tabWidth = 0) => GetLengthThatFits (text?.ToRuneList (), columns, tabWidth); - - /// - /// Gets the number of the Runes in a list of Runes that will fit in . - /// - /// The list of runes. - /// The width. - /// The number of columns used for a tab. - /// The index of the last Rune in that fit in . - public static int GetLengthThatFits (List runes, int columns, int tabWidth = 0) - { - if (runes == null || runes.Count == 0) { - return 0; - } - - var runesLength = 0; - var runeIdx = 0; - for (; runeIdx < runes.Count; runeIdx++) { - var runeWidth = GetRuneWidth (runes [runeIdx], tabWidth); - if (runesLength + runeWidth > columns) { - break; - } - runesLength += runeWidth; - } - return runeIdx; - } - - private static int GetRuneWidth (string str, int tabWidth) - { - return GetRuneWidth (str.EnumerateRunes ().ToList (), tabWidth); - } - - private static int GetRuneWidth (List runes, int tabWidth) - { - return runes.Sum (r => GetRuneWidth (r, tabWidth)); - } - - private static int GetRuneWidth (Rune rune, int tabWidth) - { - var runeWidth = rune.GetColumns (); - if (rune.Value == '\t') { - return tabWidth; - } - if (runeWidth < 0 || runeWidth > 0) { - return Math.Max (runeWidth, 1); - } - - return runeWidth; - } - - /// - /// Gets the index position from the list based on the . - /// - /// The lines. - /// The width. - /// The number of columns used for a tab. - /// The index of the list that fit the width. - public static int GetMaxColsForWidth (List lines, int width, int tabWidth = 0) - { - var runesLength = 0; - var lineIdx = 0; - for (; lineIdx < lines.Count; lineIdx++) { - var runes = lines [lineIdx].ToRuneList (); - var maxRruneWidth = runes.Count > 0 - ? runes.Max (r => GetRuneWidth (r, tabWidth)) : 1; - if (runesLength + maxRruneWidth > width) { - break; - } - runesLength += maxRruneWidth; - } - return lineIdx; - } - - /// - /// Calculates the rectangle required to hold a formatted string of text. - /// - /// The x location of the rectangle - /// The y location of the rectangle - /// The text to measure - /// The text direction. - /// The number of columns used for a tab. - /// - public static Rect CalcRect (int x, int y, string text, TextDirection direction = TextDirection.LeftRight_TopBottom, int tabWidth = 0) - { - if (string.IsNullOrEmpty (text)) { - return new Rect (new Point (x, y), Size.Empty); - } - - int w, h; - - if (IsHorizontalDirection (direction)) { - int mw = 0; - int ml = 1; - - int cols = 0; - foreach (var rune in text.EnumerateRunes ()) { - if (rune.Value == '\n') { - ml++; - if (cols > mw) { - mw = cols; - } - cols = 0; - } else if (rune.Value != '\r') { - cols++; - var rw = 0; - if (rune.Value == '\t') { - rw += tabWidth - 1; - } else { - rw = ((Rune)rune).GetColumns (); - if (rw > 0) { - rw--; - } else if (rw == 0) { - cols--; - } - } - cols += rw; - } - } - if (cols > mw) { - mw = cols; - } - w = mw; - h = ml; - } else { - int vw = 1, cw = 1; - int vh = 0; - - int rows = 0; - foreach (var rune in text.EnumerateRunes ()) { - if (rune.Value == '\n') { - vw++; - if (rows > vh) { - vh = rows; - } - rows = 0; - cw = 1; - } else if (rune.Value != '\r') { - rows++; - var rw = 0; - if (rune.Value == '\t') { - rw += tabWidth - 1; - rows += rw; - } else { - rw = ((Rune)rune).GetColumns (); - if (rw == 0) { - rows--; - } else if (cw < rw) { - cw = rw; - vw++; - } - } - } - } - if (rows > vh) { - vh = rows; - } - w = vw; - h = vh; - } - - return new Rect (x, y, w, h); - } - - /// - /// Finds the HotKey and its location in text. - /// - /// The text to look in. - /// The HotKey specifier (e.g. '_') to look for. - /// Outputs the Rune index into text. - /// Outputs the hotKey. if not found. - /// If true the legacy behavior of identifying the - /// first upper case character as the HotKey will be enabled. - /// Regardless of the value of this parameter, hotKeySpecifier takes precedence. - /// Defaults to . - /// true if a HotKey was found; false otherwise. - public static bool FindHotKey (string text, Rune hotKeySpecifier, out int hotPos, out Key hotKey, bool firstUpperCase = false) - { - if (string.IsNullOrEmpty (text) || hotKeySpecifier == (Rune)0xFFFF) { - hotPos = -1; - hotKey = KeyCode.Null; - return false; - } - - Rune hot_key = (Rune)0; - int hot_pos = -1; - - // Use first hot_key char passed into 'hotKey'. - // TODO: Ignore hot_key of two are provided - // TODO: Do not support non-alphanumeric chars that can't be typed - int i = 0; - foreach (Rune c in text.EnumerateRunes ()) { - if ((char)c.Value != 0xFFFD) { - if (c == hotKeySpecifier) { - hot_pos = i; - } else if (hot_pos > -1) { - hot_key = c; - break; - } - } - i++; - } - - // Legacy support - use first upper case char if the specifier was not found - if (hot_pos == -1 && firstUpperCase) { - i = 0; - foreach (Rune c in text.EnumerateRunes ()) { - if ((char)c.Value != 0xFFFD) { - if (Rune.IsUpper (c)) { - hot_key = c; - hot_pos = i; - break; - } - } - i++; - } - } - - if (hot_key != (Rune)0 && hot_pos != -1) { - hotPos = hot_pos; - - var newHotKey = (KeyCode)hot_key.Value; - if (newHotKey != KeyCode.Null && !(newHotKey == KeyCode.Space || Rune.IsControl (hot_key))) { - if ((newHotKey & ~KeyCode.Space) is >= KeyCode.A and <= KeyCode.Z) { - newHotKey &= ~KeyCode.Space; - } - hotKey = newHotKey; - return true; - } - } - - hotPos = -1; - hotKey = KeyCode.Null; - return false; - } - - /// - /// Replaces the Rune at the index specified by the hotPos parameter with a tag identifying - /// it as the hotkey. - /// - /// The text to tag the hotkey in. - /// The Rune index of the hotkey in text. - /// The text with the hotkey tagged. - /// - /// The returned string will not render correctly without first un-doing the tag. To undo the tag, search for - /// - public string ReplaceHotKeyWithTag (string text, int hotPos) - { - // Set the high bit - var runes = text.ToRuneList (); - if (Rune.IsLetterOrDigit (runes [hotPos])) { - runes [hotPos] = new Rune ((uint)runes [hotPos].Value); - } - return StringExtensions.ToString (runes); - } - - /// - /// Removes the hotkey specifier from text. - /// - /// The text to manipulate. - /// The hot-key specifier (e.g. '_') to look for. - /// Returns the position of the hot-key in the text. -1 if not found. - /// The input text with the hotkey specifier ('_') removed. - public static string RemoveHotKeySpecifier (string text, int hotPos, Rune hotKeySpecifier) - { - if (string.IsNullOrEmpty (text)) { - return text; - } - - // Scan - string start = string.Empty; - int i = 0; - foreach (Rune c in text) { - if (c == hotKeySpecifier && i == hotPos) { - i++; - continue; - } - start += c; - i++; - } - return start; - } - #endregion // Static Members - - List _lines = new List (); - string _text = null; - TextAlignment _textAlignment; - VerticalTextAlignment _textVerticalAlignment; - TextDirection _textDirection; - Key _hotKey = new Key (); - int _hotKeyPos = -1; - Size _size; - private bool _autoSize; - private bool _preserveTrailingSpaces; - private int _tabWidth = 4; - private bool _wordWrap = true; - private bool _multiLine; - - /// - /// Event invoked when the is changed. - /// - public event EventHandler HotKeyChanged; - - /// - /// The text to be displayed. This string is never modified. - /// - public virtual string Text { - get => _text; - set { - var textWasNull = _text == null && value != null; - _text = EnableNeedsFormat (value); - - if ((AutoSize && Alignment != TextAlignment.Justified && VerticalAlignment != VerticalTextAlignment.Justified) || (textWasNull && Size.IsEmpty)) { - Size = CalcRect (0, 0, _text, Direction, TabWidth).Size; - } - - //if (_text != null && _text.GetRuneCount () > 0 && (Size.Width == 0 || Size.Height == 0 || Size.Width != _text.GetColumns ())) { - // // Provide a default size (width = length of longest line, height = 1) - // // TODO: It might makes more sense for the default to be width = length of first line? - // Size = new Size (TextFormatter.MaxWidth (Text, int.MaxValue), 1); - //} - } - } - - /// - /// Gets or sets whether the should be automatically changed to fit the . - /// - /// - /// - /// Used by to resize the view's to fit . - /// - /// - /// AutoSize is ignored if and are used. - /// - /// - public bool AutoSize { - get => _autoSize; - set { - _autoSize = EnableNeedsFormat (value); - if (_autoSize && Alignment != TextAlignment.Justified && VerticalAlignment != VerticalTextAlignment.Justified) { - Size = CalcRect (0, 0, _text, Direction, TabWidth).Size; - } - } - } - - /// - /// Gets or sets whether trailing spaces at the end of word-wrapped lines are preserved - /// or not when is enabled. - /// If trailing spaces at the end of wrapped lines will be removed when - /// is formatted for display. The default is . - /// - public bool PreserveTrailingSpaces { - get => _preserveTrailingSpaces; - set => _preserveTrailingSpaces = EnableNeedsFormat (value); - } - - /// - /// Controls the horizontal text-alignment property. - /// - /// The text alignment. - public TextAlignment Alignment { - get => _textAlignment; - set => _textAlignment = EnableNeedsFormat (value); - } - - /// - /// Controls the vertical text-alignment property. - /// - /// The text vertical alignment. - public VerticalTextAlignment VerticalAlignment { - get => _textVerticalAlignment; - set => _textVerticalAlignment = EnableNeedsFormat (value); - } - - /// - /// Controls the text-direction property. - /// - /// The text vertical alignment. - public TextDirection Direction { - get => _textDirection; - set { - _textDirection = EnableNeedsFormat (value); - if (AutoSize && Alignment != TextAlignment.Justified && VerticalAlignment != VerticalTextAlignment.Justified) { - Size = CalcRect (0, 0, Text, Direction, TabWidth).Size; - } - } - } - - /// - /// Check if it is a horizontal direction - /// - public static bool IsHorizontalDirection (TextDirection textDirection) - { - switch (textDirection) { - case TextDirection.LeftRight_TopBottom: - case TextDirection.LeftRight_BottomTop: - case TextDirection.RightLeft_TopBottom: - case TextDirection.RightLeft_BottomTop: - return true; - default: - return false; - } - } - - /// - /// Check if it is a vertical direction - /// - public static bool IsVerticalDirection (TextDirection textDirection) - { - switch (textDirection) { - case TextDirection.TopBottom_LeftRight: - case TextDirection.TopBottom_RightLeft: - case TextDirection.BottomTop_LeftRight: - case TextDirection.BottomTop_RightLeft: - return true; - default: - return false; - } - } - - /// - /// Check if it is Left to Right direction - /// - public static bool IsLeftToRight (TextDirection textDirection) - { - switch (textDirection) { - case TextDirection.LeftRight_TopBottom: - case TextDirection.LeftRight_BottomTop: - return true; - default: - return false; - } - } - - /// - /// Check if it is Top to Bottom direction - /// - public static bool IsTopToBottom (TextDirection textDirection) - { - switch (textDirection) { - case TextDirection.TopBottom_LeftRight: - case TextDirection.TopBottom_RightLeft: - return true; - default: - return false; - } - } - - /// - /// Gets or sets whether word wrap will be used to fit to . - /// - public bool WordWrap { - get => _wordWrap; - set => _wordWrap = EnableNeedsFormat (value); - } - - /// - /// Gets or sets the size will be constrained to when formatted. - /// - /// - /// Does not return the size of the formatted text but the size that will be used to constrain the text when formatted. - /// - public Size Size { - get => _size; - set { - if (AutoSize && Alignment != TextAlignment.Justified && VerticalAlignment != VerticalTextAlignment.Justified) { - _size = EnableNeedsFormat (CalcRect (0, 0, Text, Direction, TabWidth).Size); - } else { - _size = EnableNeedsFormat (value); - } - } - } - - /// - /// The specifier character for the hot key (e.g. '_'). Set to '\xffff' to disable hot key support for this View instance. The default is '\xffff'. - /// - public Rune HotKeySpecifier { get; set; } = (Rune)0xFFFF; - - /// - /// The position in the text of the hot key. The hot key will be rendered using the hot color. - /// - public int HotKeyPos { get => _hotKeyPos; internal set => _hotKeyPos = value; } - - /// - /// Gets or sets the hot key. Must be be an upper case letter or digit. Fires the event. - /// - public Key HotKey { - get => _hotKey; - internal set { - if (_hotKey != value) { - var oldKey = _hotKey; - _hotKey = value; - HotKeyChanged?.Invoke (this, new KeyChangedEventArgs (oldKey, value)); - } - } - } - - /// - /// Gets the cursor position from . If the is defined, the cursor will be positioned over it. - /// - public int CursorPosition { get; internal set; } - - /// - /// Gets the size required to hold the formatted text, given the constraints placed by . - /// - /// - /// Causes a format, resetting . - /// - /// - public Size GetFormattedSize () - { - var lines = Lines; - if (Lines.Count > 0) { - var width = Lines.Max (line => line.GetColumns ()); - var height = Lines.Count; - return new Size (width, height); - } - return Size.Empty; - } - - /// - /// Gets the formatted lines. - /// - /// - /// - /// Upon a 'get' of this property, if the text needs to be formatted (if is true) - /// will be called internally. - /// - /// - public List Lines { - get { - // With this check, we protect against subclasses with overrides of Text - if (string.IsNullOrEmpty (Text) || Size.IsEmpty) { - _lines = new List { - string.Empty - }; - NeedsFormat = false; - return _lines; - } - - if (NeedsFormat) { - var shown_text = _text; - if (FindHotKey (_text, HotKeySpecifier, out _hotKeyPos, out var newHotKey)) { - HotKey = newHotKey; - shown_text = RemoveHotKeySpecifier (Text, _hotKeyPos, HotKeySpecifier); - shown_text = ReplaceHotKeyWithTag (shown_text, _hotKeyPos); - } - - if (IsVerticalDirection (Direction)) { - var colsWidth = GetSumMaxCharWidth (shown_text, 0, 1, TabWidth); - _lines = Format (shown_text, Size.Height, VerticalAlignment == VerticalTextAlignment.Justified, Size.Width > colsWidth && WordWrap, - PreserveTrailingSpaces, TabWidth, Direction, MultiLine); - if (!AutoSize) { - colsWidth = GetMaxColsForWidth (_lines, Size.Width, TabWidth); - if (_lines.Count > colsWidth) { - _lines.RemoveRange (colsWidth, _lines.Count - colsWidth); - } - } - } else { - _lines = Format (shown_text, Size.Width, Alignment == TextAlignment.Justified, Size.Height > 1 && WordWrap, - PreserveTrailingSpaces, TabWidth, Direction, MultiLine); - if (!AutoSize && _lines.Count > Size.Height) { - _lines.RemoveRange (Size.Height, _lines.Count - Size.Height); - } - } - - NeedsFormat = false; - } - return _lines; - } - } - - /// - /// Gets or sets whether the needs to format the text. - /// - /// - /// - /// If false when Draw is called, the Draw call will be faster. - /// - /// - /// Used by - /// - /// - /// This is set to true when the properties of are set. - /// - /// - public bool NeedsFormat { get; set; } - - /// - /// Gets or sets the number of columns used for a tab. - /// - public int TabWidth { - get => _tabWidth; - set => _tabWidth = EnableNeedsFormat (value); - } - - /// - /// Gets or sets a value indicating whether multi line is allowed. - /// - /// - /// Multi line is ignored if is . - /// - public bool MultiLine { - get => _multiLine; - set { - _multiLine = EnableNeedsFormat (value); - } - } - - private T EnableNeedsFormat (T value) - { - NeedsFormat = true; - return value; - } - - /// - /// Causes the to reformat the text. - /// - /// The formatted text. - public string Format () - { - var sb = new StringBuilder (); - // Lines_get causes a Format - foreach (var line in Lines) { - sb.AppendLine (line); - } - return sb.ToString ().TrimEnd (Environment.NewLine.ToCharArray ()); - } - - /// - /// Draws the text held by to using the colors specified. - /// - /// Specifies the screen-relative location and maximum size for drawing the text. - /// The color to use for all text except the hotkey - /// The color to use to draw the hotkey - /// Specifies the screen-relative location and maximum container size. - /// Determines if the bounds width will be used (default) or only the text width will be used. - /// The console driver currently used by the application. - public void Draw (Rect bounds, Attribute normalColor, Attribute hotColor, Rect containerBounds = default, bool fillRemaining = true, ConsoleDriver driver = null) - { - // With this check, we protect against subclasses with overrides of Text (like Button) - if (string.IsNullOrEmpty (_text)) { - return; - } - - if (driver == null) { - driver = Application.Driver; - } - driver?.SetAttribute (normalColor); - - // Use "Lines" to ensure a Format (don't use "lines")) - - var linesFormated = Lines; - switch (Direction) { - case TextDirection.TopBottom_RightLeft: - case TextDirection.LeftRight_BottomTop: - case TextDirection.RightLeft_BottomTop: - case TextDirection.BottomTop_RightLeft: - linesFormated.Reverse (); - break; - } - - var isVertical = IsVerticalDirection (Direction); - var maxBounds = bounds; - if (driver != null) { - maxBounds = containerBounds == default - ? bounds - : new Rect (Math.Max (containerBounds.X, bounds.X), - Math.Max (containerBounds.Y, bounds.Y), - Math.Max (Math.Min (containerBounds.Width, containerBounds.Right - bounds.Left), 0), - Math.Max (Math.Min (containerBounds.Height, containerBounds.Bottom - bounds.Top), 0)); - } - if (maxBounds.Width == 0 || maxBounds.Height == 0) { - return; - } - - // BUGBUG: v2 - TextFormatter should not change the clip region. If a caller wants to break out of the clip region it should do - // so explicitly. - //var savedClip = Application.Driver?.Clip; - //if (Application.Driver != null) { - // Application.Driver.Clip = maxBounds; - //} - var lineOffset = !isVertical && bounds.Y < 0 ? Math.Abs (bounds.Y) : 0; - - for (int line = lineOffset; line < linesFormated.Count; line++) { - if ((isVertical && line > bounds.Width) || (!isVertical && line > bounds.Height)) - continue; - if ((isVertical && line >= maxBounds.Left + maxBounds.Width) - || (!isVertical && line >= maxBounds.Top + maxBounds.Height + lineOffset)) - - break; - - var runes = _lines [line].ToRunes (); - - switch (Direction) { - case TextDirection.RightLeft_BottomTop: - case TextDirection.RightLeft_TopBottom: - case TextDirection.BottomTop_LeftRight: - case TextDirection.BottomTop_RightLeft: - runes = runes.Reverse ().ToArray (); - break; - } - - // When text is justified, we lost left or right, so we use the direction to align. - - int x, y; - // Horizontal Alignment - if (_textAlignment == TextAlignment.Right || (_textAlignment == TextAlignment.Justified && !IsLeftToRight (Direction))) { - if (isVertical) { - var runesWidth = GetSumMaxCharWidth (Lines, line, TabWidth); - x = bounds.Right - runesWidth; - CursorPosition = bounds.Width - runesWidth + (_hotKeyPos > -1 ? _hotKeyPos : 0); - } else { - var runesWidth = StringExtensions.ToString (runes).GetColumns (); - x = bounds.Right - runesWidth; - CursorPosition = bounds.Width - runesWidth + (_hotKeyPos > -1 ? _hotKeyPos : 0); - } - } else if (_textAlignment == TextAlignment.Left || _textAlignment == TextAlignment.Justified) { - if (isVertical) { - var runesWidth = line > 0 ? GetSumMaxCharWidth (Lines, 0, line, TabWidth) : 0; - x = bounds.Left + runesWidth; - } else { - x = bounds.Left; - } - CursorPosition = _hotKeyPos > -1 ? _hotKeyPos : 0; - } else if (_textAlignment == TextAlignment.Centered) { - if (isVertical) { - var runesWidth = GetSumMaxCharWidth (Lines, line, TabWidth); - x = bounds.Left + line + ((bounds.Width - runesWidth) / 2); - CursorPosition = (bounds.Width - runesWidth) / 2 + (_hotKeyPos > -1 ? _hotKeyPos : 0); - } else { - var runesWidth = StringExtensions.ToString (runes).GetColumns (); - x = bounds.Left + (bounds.Width - runesWidth) / 2; - CursorPosition = (bounds.Width - runesWidth) / 2 + (_hotKeyPos > -1 ? _hotKeyPos : 0); - } - } else { - throw new ArgumentOutOfRangeException (); - } - - // Vertical Alignment - if (_textVerticalAlignment == VerticalTextAlignment.Bottom || (_textVerticalAlignment == VerticalTextAlignment.Justified && !IsTopToBottom (Direction))) { - if (isVertical) { - y = bounds.Bottom - runes.Length; - } else { - y = bounds.Bottom - Lines.Count + line; - } - } else if (_textVerticalAlignment == VerticalTextAlignment.Top || _textVerticalAlignment == VerticalTextAlignment.Justified) { - if (isVertical) { - y = bounds.Top; - } else { - y = bounds.Top + line; - } - } else if (_textVerticalAlignment == VerticalTextAlignment.Middle) { - if (isVertical) { - var s = (bounds.Height - runes.Length) / 2; - y = bounds.Top + s; - } else { - var s = (bounds.Height - Lines.Count) / 2; - y = bounds.Top + line + s; - } - } else { - throw new ArgumentOutOfRangeException (); - } - - var colOffset = bounds.X < 0 ? Math.Abs (bounds.X) : 0; - var start = isVertical ? bounds.Top : bounds.Left; - var size = isVertical ? bounds.Height : bounds.Width; - var current = start + colOffset; - List lastZeroWidthPos = null; - Rune rune = default; - Rune lastRuneUsed; - var zeroLengthCount = isVertical ? runes.Sum (r => r.GetColumns () == 0 ? 1 : 0) : 0; - - for (var idx = (isVertical ? start - y : start - x) + colOffset; current < start + size + zeroLengthCount; idx++) { - lastRuneUsed = rune; - if (lastZeroWidthPos == null) { - if (idx < 0 || x + current + colOffset < 0) { - current++; - continue; - } else if (!fillRemaining && idx > runes.Length - 1) { - break; - } - if ((!isVertical && current - start > maxBounds.Left + maxBounds.Width - bounds.X + colOffset) - || (isVertical && idx > maxBounds.Top + maxBounds.Height - bounds.Y)) { - - break; - } - } - //if ((!isVertical && idx > maxBounds.Left + maxBounds.Width - bounds.X + colOffset) - // || (isVertical && idx > maxBounds.Top + maxBounds.Height - bounds.Y)) - - // break; - - rune = (Rune)' '; - if (isVertical) { - if (idx >= 0 && idx < runes.Length) { - rune = runes [idx]; - } - if (lastZeroWidthPos == null) { - driver?.Move (x, current); - } else { - var foundIdx = lastZeroWidthPos.IndexOf (p => p.Value.Y == current); - if (foundIdx > -1) { - if (rune.IsCombiningMark ()) { - lastZeroWidthPos [foundIdx] = (new Point (lastZeroWidthPos [foundIdx].Value.X + 1, current)); - - driver?.Move (lastZeroWidthPos [foundIdx].Value.X, current); - } else if (!rune.IsCombiningMark () && lastRuneUsed.IsCombiningMark ()) { - current++; - driver?.Move (x, current); - } else { - driver?.Move (x, current); - } - } else { - driver?.Move (x, current); - } - } - } else { - driver?.Move (current, y); - if (idx >= 0 && idx < runes.Length) { - rune = runes [idx]; - } - } - - var runeWidth = GetRuneWidth (rune, TabWidth); - - if (HotKeyPos > -1 && idx == HotKeyPos) { - if ((isVertical && _textVerticalAlignment == VerticalTextAlignment.Justified) || - (!isVertical && _textAlignment == TextAlignment.Justified)) { - CursorPosition = idx - start; - } - driver?.SetAttribute (hotColor); - driver?.AddRune (rune); - driver?.SetAttribute (normalColor); - } else { - if (isVertical) { - if (runeWidth == 0) { - if (lastZeroWidthPos == null) { - lastZeroWidthPos = new List (); - } - var foundIdx = lastZeroWidthPos.IndexOf (p => p.Value.Y == current); - if (foundIdx == -1) { - current--; - lastZeroWidthPos.Add ((new Point (x + 1, current))); - } - driver?.Move (x + 1, current); - } - } - - driver?.AddRune (rune); - } - - if (isVertical) { - if (runeWidth > 0) { - current++; - } - } else { - current += runeWidth; - } - var nextRuneWidth = idx + 1 > -1 && idx + 1 < runes.Length ? runes [idx + 1].GetColumns () : 0; - if (!isVertical && idx + 1 < runes.Length && current + nextRuneWidth > start + size) { - break; - } - } - } - //if (Application.Driver != null) { - // Application.Driver.Clip = (Rect)savedClip; - //} - } - } + /// + /// The text will be justified (spaces will be added to existing spaces such that the text fills the container + /// horizontally). + /// + Justified +} + +/// Vertical text alignment enumeration, controls how text is displayed. +public enum VerticalTextAlignment +{ + /// The text will be top-aligned. + Top, + + /// The text will be bottom-aligned. + Bottom, + + /// The text will centered vertically. + Middle, + + /// + /// The text will be justified (spaces will be added to existing spaces such that the text fills the container + /// vertically). + /// + Justified +} + +/// Text direction enumeration, controls how text is displayed. +/// +/// TextDirection [H] = Horizontal [V] = Vertical +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +///
TextDirection Description
LeftRight_TopBottom [H] Normal
TopBottom_LeftRight [V] Normal
RightLeft_TopBottom [H] Invert Text
TopBottom_RightLeft [V] Invert Lines
LeftRight_BottomTop [H] Invert Lines
BottomTop_LeftRight [V] Invert Text
RightLeft_BottomTop [H] Invert Text + Invert Lines
BottomTop_RightLeft [V] Invert Text + Invert Lines
+///
+public enum TextDirection +{ + /// Normal horizontal direction. HELLO
WORLD
+ LeftRight_TopBottom, + + /// Normal vertical direction. H W
E O
L R
L L
O D
+ TopBottom_LeftRight, + + /// This is a horizontal direction.
RTL OLLEH
DLROW
+ RightLeft_TopBottom, + + /// This is a vertical direction. W H
O E
R L
L L
D O
+ TopBottom_RightLeft, + + /// This is a horizontal direction. WORLD
HELLO
+ LeftRight_BottomTop, + + /// This is a vertical direction. O D
L L
L R
E O
H W
+ BottomTop_LeftRight, + + /// This is a horizontal direction. DLROW
OLLEH
+ RightLeft_BottomTop, + + /// This is a vertical direction. D O
L L
R L
O E
W H
+ BottomTop_RightLeft +} + +/// +/// Provides text formatting. Supports s, horizontal alignment, vertical alignment, multiple +/// lines, and word-based line wrap. +/// +public class TextFormatter +{ + private bool _autoSize; + private Key _hotKey = new (); + private int _hotKeyPos = -1; + + private List _lines = new (); + private bool _multiLine; + private bool _preserveTrailingSpaces; + private Size _size; + private int _tabWidth = 4; + private string _text; + private TextAlignment _textAlignment; + private TextDirection _textDirection; + private VerticalTextAlignment _textVerticalAlignment; + private bool _wordWrap = true; + + /// Controls the horizontal text-alignment property. + /// The text alignment. + public TextAlignment Alignment { get => _textAlignment; set => _textAlignment = EnableNeedsFormat (value); } + + /// Gets or sets whether the should be automatically changed to fit the . + /// + /// Used by to resize the view's to fit . + /// + /// AutoSize is ignored if and + /// are used. + /// + /// + public bool AutoSize + { + get => _autoSize; + set + { + _autoSize = EnableNeedsFormat (value); + + if (_autoSize && Alignment != TextAlignment.Justified && VerticalAlignment != VerticalTextAlignment.Justified) + { + Size = CalcRect (0, 0, _text, Direction, TabWidth).Size; + } + } + } + + /// + /// Gets the cursor position from . If the is defined, the cursor will be + /// positioned over it. + /// + public int CursorPosition { get; internal set; } + + /// Controls the text-direction property. + /// The text vertical alignment. + public TextDirection Direction + { + get => _textDirection; + set + { + _textDirection = EnableNeedsFormat (value); + + if (AutoSize && Alignment != TextAlignment.Justified && VerticalAlignment != VerticalTextAlignment.Justified) + { + Size = CalcRect (0, 0, Text, Direction, TabWidth).Size; + } + } + } + + /// + /// Gets or sets the hot key. Must be be an upper case letter or digit. Fires the + /// event. + /// + public Key HotKey + { + get => _hotKey; + internal set + { + if (_hotKey != value) + { + Key oldKey = _hotKey; + _hotKey = value; + HotKeyChanged?.Invoke (this, new KeyChangedEventArgs (oldKey, value)); + } + } + } + + /// The position in the text of the hot key. The hot key will be rendered using the hot color. + public int HotKeyPos { get => _hotKeyPos; internal set => _hotKeyPos = value; } + + /// + /// The specifier character for the hot key (e.g. '_'). Set to '\xffff' to disable hot key support for this View + /// instance. The default is '\xffff'. + /// + public Rune HotKeySpecifier { get; set; } = (Rune)0xFFFF; + + /// Gets the formatted lines. + /// + /// + /// Upon a 'get' of this property, if the text needs to be formatted (if is true) + /// will be called internally. + /// + /// + public List Lines + { + get + { + // With this check, we protect against subclasses with overrides of Text + if (string.IsNullOrEmpty (Text) || Size.IsEmpty) + { + _lines = new List + { + string.Empty + }; + NeedsFormat = false; + + return _lines; + } + + if (NeedsFormat) + { + string shown_text = _text; + + if (FindHotKey (_text, HotKeySpecifier, out _hotKeyPos, out Key newHotKey)) + { + HotKey = newHotKey; + shown_text = RemoveHotKeySpecifier (Text, _hotKeyPos, HotKeySpecifier); + shown_text = ReplaceHotKeyWithTag (shown_text, _hotKeyPos); + } + + if (IsVerticalDirection (Direction)) + { + int colsWidth = GetSumMaxCharWidth (shown_text, 0, 1, TabWidth); + + _lines = Format ( + shown_text, + Size.Height, + VerticalAlignment == VerticalTextAlignment.Justified, + Size.Width > colsWidth && WordWrap, + PreserveTrailingSpaces, + TabWidth, + Direction, + MultiLine); + + if (!AutoSize) + { + colsWidth = GetMaxColsForWidth (_lines, Size.Width, TabWidth); + + if (_lines.Count > colsWidth) + { + _lines.RemoveRange (colsWidth, _lines.Count - colsWidth); + } + } + } + else + { + _lines = Format ( + shown_text, + Size.Width, + Alignment == TextAlignment.Justified, + Size.Height > 1 && WordWrap, + PreserveTrailingSpaces, + TabWidth, + Direction, + MultiLine); + + if (!AutoSize && _lines.Count > Size.Height) + { + _lines.RemoveRange (Size.Height, _lines.Count - Size.Height); + } + } + + NeedsFormat = false; + } + + return _lines; + } + } + + /// Gets or sets a value indicating whether multi line is allowed. + /// Multi line is ignored if is . + public bool MultiLine { get => _multiLine; set => _multiLine = EnableNeedsFormat (value); } + + /// Gets or sets whether the needs to format the text. + /// + /// If false when Draw is called, the Draw call will be faster. + /// Used by + /// This is set to true when the properties of are set. + /// + public bool NeedsFormat { get; set; } + + /// + /// Gets or sets whether trailing spaces at the end of word-wrapped lines are preserved or not when + /// is enabled. If trailing spaces at the end of wrapped + /// lines will be removed when is formatted for display. The default is . + /// + public bool PreserveTrailingSpaces { get => _preserveTrailingSpaces; set => _preserveTrailingSpaces = EnableNeedsFormat (value); } + + /// Gets or sets the size will be constrained to when formatted. + /// + /// Does not return the size of the formatted text but the size that will be used to constrain the text when + /// formatted. + /// + public Size Size + { + get => _size; + set + { + if (AutoSize && Alignment != TextAlignment.Justified && VerticalAlignment != VerticalTextAlignment.Justified) + { + _size = EnableNeedsFormat (CalcRect (0, 0, Text, Direction, TabWidth).Size); + } + else + { + _size = EnableNeedsFormat (value); + } + } + } + + /// Gets or sets the number of columns used for a tab. + public int TabWidth { get => _tabWidth; set => _tabWidth = EnableNeedsFormat (value); } + + /// The text to be displayed. This string is never modified. + public virtual string Text + { + get => _text; + set + { + bool textWasNull = _text == null && value != null; + _text = EnableNeedsFormat (value); + + if ((AutoSize && Alignment != TextAlignment.Justified && VerticalAlignment != VerticalTextAlignment.Justified) || (textWasNull && Size.IsEmpty)) + { + Size = CalcRect (0, 0, _text, Direction, TabWidth).Size; + } + + //if (_text != null && _text.GetRuneCount () > 0 && (Size.Width == 0 || Size.Height == 0 || Size.Width != _text.GetColumns ())) { + // // Provide a default size (width = length of longest line, height = 1) + // // TODO: It might makes more sense for the default to be width = length of first line? + // Size = new Size (TextFormatter.MaxWidth (Text, int.MaxValue), 1); + //} + } + } + + /// Controls the vertical text-alignment property. + /// The text vertical alignment. + public VerticalTextAlignment VerticalAlignment { get => _textVerticalAlignment; set => _textVerticalAlignment = EnableNeedsFormat (value); } + + /// Gets or sets whether word wrap will be used to fit to . + public bool WordWrap { get => _wordWrap; set => _wordWrap = EnableNeedsFormat (value); } + + /// Draws the text held by to using the colors specified. + /// Specifies the screen-relative location and maximum size for drawing the text. + /// The color to use for all text except the hotkey + /// The color to use to draw the hotkey + /// Specifies the screen-relative location and maximum container size. + /// Determines if the bounds width will be used (default) or only the text width will be used. + /// The console driver currently used by the application. + public void Draw ( + Rect bounds, + Attribute normalColor, + Attribute hotColor, + Rect containerBounds = default, + bool fillRemaining = true, + ConsoleDriver driver = null + ) + { + // With this check, we protect against subclasses with overrides of Text (like Button) + if (string.IsNullOrEmpty (_text)) + { + return; + } + + if (driver == null) + { + driver = Application.Driver; + } + + driver?.SetAttribute (normalColor); + + // Use "Lines" to ensure a Format (don't use "lines")) + + List linesFormated = Lines; + + switch (Direction) + { + case TextDirection.TopBottom_RightLeft: + case TextDirection.LeftRight_BottomTop: + case TextDirection.RightLeft_BottomTop: + case TextDirection.BottomTop_RightLeft: + linesFormated.Reverse (); + + break; + } + + bool isVertical = IsVerticalDirection (Direction); + Rect maxBounds = bounds; + + if (driver != null) + { + maxBounds = containerBounds == default (Rect) + ? bounds + : new Rect ( + Math.Max (containerBounds.X, bounds.X), + Math.Max (containerBounds.Y, bounds.Y), + Math.Max (Math.Min (containerBounds.Width, containerBounds.Right - bounds.Left), 0), + Math.Max (Math.Min (containerBounds.Height, containerBounds.Bottom - bounds.Top), 0)); + } + + if ((maxBounds.Width == 0) || (maxBounds.Height == 0)) + { + return; + } + + // BUGBUG: v2 - TextFormatter should not change the clip region. If a caller wants to break out of the clip region it should do + // so explicitly. + //var savedClip = Application.Driver?.Clip; + //if (Application.Driver != null) { + // Application.Driver.Clip = maxBounds; + //} + int lineOffset = !isVertical && bounds.Y < 0 ? Math.Abs (bounds.Y) : 0; + + for (int line = lineOffset; line < linesFormated.Count; line++) + { + if ((isVertical && line > bounds.Width) || (!isVertical && line > bounds.Height)) + { + continue; + } + + if ((isVertical && line >= maxBounds.Left + maxBounds.Width) + || (!isVertical && line >= maxBounds.Top + maxBounds.Height + lineOffset)) + + { + break; + } + + Rune [] runes = _lines [line].ToRunes (); + + switch (Direction) + { + case TextDirection.RightLeft_BottomTop: + case TextDirection.RightLeft_TopBottom: + case TextDirection.BottomTop_LeftRight: + case TextDirection.BottomTop_RightLeft: + runes = runes.Reverse ().ToArray (); + + break; + } + + // When text is justified, we lost left or right, so we use the direction to align. + + int x, y; + + // Horizontal Alignment + if ((_textAlignment == TextAlignment.Right) || (_textAlignment == TextAlignment.Justified && !IsLeftToRight (Direction))) + { + if (isVertical) + { + int runesWidth = GetSumMaxCharWidth (Lines, line, TabWidth); + x = bounds.Right - runesWidth; + CursorPosition = bounds.Width - runesWidth + (_hotKeyPos > -1 ? _hotKeyPos : 0); + } + else + { + int runesWidth = StringExtensions.ToString (runes).GetColumns (); + x = bounds.Right - runesWidth; + CursorPosition = bounds.Width - runesWidth + (_hotKeyPos > -1 ? _hotKeyPos : 0); + } + } + else if ((_textAlignment == TextAlignment.Left) || (_textAlignment == TextAlignment.Justified)) + { + if (isVertical) + { + int runesWidth = line > 0 ? GetSumMaxCharWidth (Lines, 0, line, TabWidth) : 0; + x = bounds.Left + runesWidth; + } + else + { + x = bounds.Left; + } + + CursorPosition = _hotKeyPos > -1 ? _hotKeyPos : 0; + } + else if (_textAlignment == TextAlignment.Centered) + { + if (isVertical) + { + int runesWidth = GetSumMaxCharWidth (Lines, line, TabWidth); + x = bounds.Left + line + (bounds.Width - runesWidth) / 2; + CursorPosition = (bounds.Width - runesWidth) / 2 + (_hotKeyPos > -1 ? _hotKeyPos : 0); + } + else + { + int runesWidth = StringExtensions.ToString (runes).GetColumns (); + x = bounds.Left + (bounds.Width - runesWidth) / 2; + CursorPosition = (bounds.Width - runesWidth) / 2 + (_hotKeyPos > -1 ? _hotKeyPos : 0); + } + } + else + { + throw new ArgumentOutOfRangeException (); + } + + // Vertical Alignment + if ((_textVerticalAlignment == VerticalTextAlignment.Bottom) + || (_textVerticalAlignment == VerticalTextAlignment.Justified && !IsTopToBottom (Direction))) + { + if (isVertical) + { + y = bounds.Bottom - runes.Length; + } + else + { + y = bounds.Bottom - Lines.Count + line; + } + } + else if ((_textVerticalAlignment == VerticalTextAlignment.Top) || (_textVerticalAlignment == VerticalTextAlignment.Justified)) + { + if (isVertical) + { + y = bounds.Top; + } + else + { + y = bounds.Top + line; + } + } + else if (_textVerticalAlignment == VerticalTextAlignment.Middle) + { + if (isVertical) + { + int s = (bounds.Height - runes.Length) / 2; + y = bounds.Top + s; + } + else + { + int s = (bounds.Height - Lines.Count) / 2; + y = bounds.Top + line + s; + } + } + else + { + throw new ArgumentOutOfRangeException (); + } + + int colOffset = bounds.X < 0 ? Math.Abs (bounds.X) : 0; + int start = isVertical ? bounds.Top : bounds.Left; + int size = isVertical ? bounds.Height : bounds.Width; + int current = start + colOffset; + List lastZeroWidthPos = null; + Rune rune = default; + Rune lastRuneUsed; + int zeroLengthCount = isVertical ? runes.Sum (r => r.GetColumns () == 0 ? 1 : 0) : 0; + + for (int idx = (isVertical ? start - y : start - x) + colOffset; current < start + size + zeroLengthCount; idx++) + { + lastRuneUsed = rune; + + if (lastZeroWidthPos == null) + { + if ((idx < 0) || (x + current + colOffset < 0)) + { + current++; + + continue; + } + + if (!fillRemaining && idx > runes.Length - 1) + { + break; + } + + if ((!isVertical && current - start > maxBounds.Left + maxBounds.Width - bounds.X + colOffset) + || (isVertical && idx > maxBounds.Top + maxBounds.Height - bounds.Y)) + { + break; + } + } + + //if ((!isVertical && idx > maxBounds.Left + maxBounds.Width - bounds.X + colOffset) + // || (isVertical && idx > maxBounds.Top + maxBounds.Height - bounds.Y)) + + // break; + + rune = (Rune)' '; + + if (isVertical) + { + if (idx >= 0 && idx < runes.Length) + { + rune = runes [idx]; + } + + if (lastZeroWidthPos == null) + { + driver?.Move (x, current); + } + else + { + int foundIdx = lastZeroWidthPos.IndexOf (p => p.Value.Y == current); + + if (foundIdx > -1) + { + if (rune.IsCombiningMark ()) + { + lastZeroWidthPos [foundIdx] = new Point (lastZeroWidthPos [foundIdx].Value.X + 1, current); + + driver?.Move (lastZeroWidthPos [foundIdx].Value.X, current); + } + else if (!rune.IsCombiningMark () && lastRuneUsed.IsCombiningMark ()) + { + current++; + driver?.Move (x, current); + } + else + { + driver?.Move (x, current); + } + } + else + { + driver?.Move (x, current); + } + } + } + else + { + driver?.Move (current, y); + + if (idx >= 0 && idx < runes.Length) + { + rune = runes [idx]; + } + } + + int runeWidth = GetRuneWidth (rune, TabWidth); + + if (HotKeyPos > -1 && idx == HotKeyPos) + { + if ((isVertical && _textVerticalAlignment == VerticalTextAlignment.Justified) || (!isVertical && _textAlignment == TextAlignment.Justified)) + { + CursorPosition = idx - start; + } + + driver?.SetAttribute (hotColor); + driver?.AddRune (rune); + driver?.SetAttribute (normalColor); + } + else + { + if (isVertical) + { + if (runeWidth == 0) + { + if (lastZeroWidthPos == null) + { + lastZeroWidthPos = new List (); + } + + int foundIdx = lastZeroWidthPos.IndexOf (p => p.Value.Y == current); + + if (foundIdx == -1) + { + current--; + lastZeroWidthPos.Add (new Point (x + 1, current)); + } + + driver?.Move (x + 1, current); + } + } + + driver?.AddRune (rune); + } + + if (isVertical) + { + if (runeWidth > 0) + { + current++; + } + } + else + { + current += runeWidth; + } + + int nextRuneWidth = idx + 1 > -1 && idx + 1 < runes.Length ? runes [idx + 1].GetColumns () : 0; + + if (!isVertical && idx + 1 < runes.Length && current + nextRuneWidth > start + size) + { + break; + } + } + } + + //if (Application.Driver != null) { + // Application.Driver.Clip = (Rect)savedClip; + //} + } + + /// Causes the to reformat the text. + /// The formatted text. + public string Format () + { + var sb = new StringBuilder (); + + // Lines_get causes a Format + foreach (string line in Lines) + { + sb.AppendLine (line); + } + + return sb.ToString ().TrimEnd (Environment.NewLine.ToCharArray ()); + } + + /// Gets the size required to hold the formatted text, given the constraints placed by . + /// Causes a format, resetting . + /// + public Size GetFormattedSize () + { + List lines = Lines; + + if (Lines.Count > 0) + { + int width = Lines.Max (line => line.GetColumns ()); + int height = Lines.Count; + + return new Size (width, height); + } + + return Size.Empty; + } + + /// Event invoked when the is changed. + public event EventHandler HotKeyChanged; + + /// Check if it is a horizontal direction + public static bool IsHorizontalDirection (TextDirection textDirection) + { + switch (textDirection) + { + case TextDirection.LeftRight_TopBottom: + case TextDirection.LeftRight_BottomTop: + case TextDirection.RightLeft_TopBottom: + case TextDirection.RightLeft_BottomTop: + return true; + default: + return false; + } + } + + /// Check if it is Left to Right direction + public static bool IsLeftToRight (TextDirection textDirection) + { + switch (textDirection) + { + case TextDirection.LeftRight_TopBottom: + case TextDirection.LeftRight_BottomTop: + return true; + default: + return false; + } + } + + /// Check if it is Top to Bottom direction + public static bool IsTopToBottom (TextDirection textDirection) + { + switch (textDirection) + { + case TextDirection.TopBottom_LeftRight: + case TextDirection.TopBottom_RightLeft: + return true; + default: + return false; + } + } + + /// Check if it is a vertical direction + public static bool IsVerticalDirection (TextDirection textDirection) + { + switch (textDirection) + { + case TextDirection.TopBottom_LeftRight: + case TextDirection.TopBottom_RightLeft: + case TextDirection.BottomTop_LeftRight: + case TextDirection.BottomTop_RightLeft: + return true; + default: + return false; + } + } + + private T EnableNeedsFormat (T value) + { + NeedsFormat = true; + + return value; + } + + #region Static Members + + private static string StripCRLF (string str, bool keepNewLine = false) + { + List runes = str.ToRuneList (); + + for (var i = 0; i < runes.Count; i++) + { + switch ((char)runes [i].Value) + { + case '\n': + if (!keepNewLine) + { + runes.RemoveAt (i); + } + + break; + + case '\r': + if (i + 1 < runes.Count && runes [i + 1].Value == '\n') + { + runes.RemoveAt (i); + + if (!keepNewLine) + { + runes.RemoveAt (i); + } + + i++; + } + else + { + if (!keepNewLine) + { + runes.RemoveAt (i); + } + } + + break; + } + } + + return StringExtensions.ToString (runes); + } + + private static string ReplaceCRLFWithSpace (string str) + { + List runes = str.ToRuneList (); + + for (var i = 0; i < runes.Count; i++) + { + switch (runes [i].Value) + { + case '\n': + runes [i] = (Rune)' '; + + break; + + case '\r': + if (i + 1 < runes.Count && runes [i + 1].Value == '\n') + { + runes [i] = (Rune)' '; + runes.RemoveAt (i + 1); + i++; + } + else + { + runes [i] = (Rune)' '; + } + + break; + } + } + + return StringExtensions.ToString (runes); + } + + private static string ReplaceTABWithSpaces (string str, int tabWidth) + { + if (tabWidth == 0) + { + return str.Replace ("\t", ""); + } + + return str.Replace ("\t", new string (' ', tabWidth)); + } + + /// + /// Splits all newlines in the into a list and supports both CRLF and LF, preserving the ending + /// newline. + /// + /// The text. + /// A list of text without the newline characters. + public static List SplitNewLine (string text) + { + List runes = text.ToRuneList (); + List lines = new (); + var start = 0; + var end = 0; + + for (var i = 0; i < runes.Count; i++) + { + end = i; + + switch (runes [i].Value) + { + case '\n': + lines.Add (StringExtensions.ToString (runes.GetRange (start, end - start))); + i++; + start = i; + + break; + + case '\r': + if (i + 1 < runes.Count && runes [i + 1].Value == '\n') + { + lines.Add (StringExtensions.ToString (runes.GetRange (start, end - start))); + i += 2; + start = i; + } + else + { + lines.Add (StringExtensions.ToString (runes.GetRange (start, end - start))); + i++; + start = i; + } + + break; + } + } + + if (runes.Count > 0 && lines.Count == 0) + { + lines.Add (StringExtensions.ToString (runes)); + } + else if (runes.Count > 0 && start < runes.Count) + { + lines.Add (StringExtensions.ToString (runes.GetRange (start, runes.Count - start))); + } + else + { + lines.Add (""); + } + + return lines; + } + + /// + /// Adds trailing whitespace or truncates so that it fits exactly + /// console units. Note that some unicode characters take 2+ columns + /// + /// + /// + /// + public static string ClipOrPad (string text, int width) + { + if (string.IsNullOrEmpty (text)) + { + return text; + } + + // if value is not wide enough + if (text.EnumerateRunes ().Sum (c => c.GetColumns ()) < width) + { + // pad it out with spaces to the given alignment + int toPad = width - text.EnumerateRunes ().Sum (c => c.GetColumns ()); + + return text + new string (' ', toPad); + } + + // value is too wide + return new string (text.TakeWhile (c => (width -= ((Rune)c).GetColumns ()) >= 0).ToArray ()); + } + + /// Formats the provided text to fit within the width provided using word wrapping. + /// The text to word wrap + /// The number of columns to constrain the text to + /// + /// If trailing spaces at the end of wrapped lines will be preserved. If + /// , trailing spaces at the end of wrapped lines will be trimmed. + /// + /// The number of columns used for a tab. + /// The text direction. + /// A list of word wrapped lines. + /// + /// This method does not do any justification. + /// This method strips Newline ('\n' and '\r\n') sequences before processing. + /// + /// If is at most one space will be preserved at + /// the end of the last line. + /// + /// + public static List WordWrapText ( + string text, + int width, + bool preserveTrailingSpaces = false, + int tabWidth = 0, + TextDirection textDirection = TextDirection.LeftRight_TopBottom + ) + { + if (width < 0) + { + throw new ArgumentOutOfRangeException ("Width cannot be negative."); + } + + int start = 0, end; + List lines = new (); + + if (string.IsNullOrEmpty (text)) + { + return lines; + } + + List runes = StripCRLF (text).ToRuneList (); + + if (preserveTrailingSpaces) + { + while ((end = start) < runes.Count) + { + end = GetNextWhiteSpace (start, width, out bool incomplete); + + if (end == 0 && incomplete) + { + start = text.GetRuneCount (); + + break; + } + + lines.Add (StringExtensions.ToString (runes.GetRange (start, end - start))); + start = end; + + if (incomplete) + { + start = text.GetRuneCount (); + + break; + } + } + } + else + { + if (IsHorizontalDirection (textDirection)) + { + //if (GetLengthThatFits (runes.GetRange (start, runes.Count - start), width) > 0) { + // // while there's still runes left and end is not past end... + // while (start < runes.Count && + // (end = start + Math.Max (GetLengthThatFits (runes.GetRange (start, runes.Count - start), width) - 1, 0)) < runes.Count) { + // // end now points to start + LengthThatFits + // // Walk back over trailing spaces + // while (runes [end] == ' ' && end > start) { + // end--; + // } + // // end now points to start + LengthThatFits - any trailing spaces; start saving new line + // var line = runes.GetRange (start, end - start + 1); + + // if (end == start && width > 1) { + // // it was all trailing spaces; now walk forward to next non-space + // do { + // start++; + // } while (start < runes.Count && runes [start] == ' '); + + // // start now points to first non-space we haven't seen yet or we're done + // if (start < runes.Count) { + // // we're not done. we have remaining = width - line.Count columns left; + // var remaining = width - line.Count; + // if (remaining > 1) { + // // add a space for all the spaces we walked over + // line.Add (' '); + // } + // var count = GetLengthThatFits (runes.GetRange (start, runes.Count - start), width - line.Count); + + // // [start..count] now has rest of line + // line.AddRange (runes.GetRange (start, count)); + // start += count; + // } + // } else { + // start += line.Count; + // } + + // //// if the previous line was just a ' ' and the new line is just a ' ' + // //// don't add new line + // //if (line [0] == ' ' && (lines.Count > 0 && lines [lines.Count - 1] [0] == ' ')) { + // //} else { + // //} + // lines.Add (string.Make (line)); + + // // move forward to next non-space + // while (width > 1 && start < runes.Count && runes [start] == ' ') { + // start++; + // } + // } + //} + + while ((end = start + GetLengthThatFits (runes.GetRange (start, runes.Count - start), width, tabWidth)) < runes.Count) + { + while (runes [end].Value != ' ' && end > start) + { + end--; + } + + if (end == start) + { + end = start + GetLengthThatFits (runes.GetRange (end, runes.Count - end), width, tabWidth); + } + + var str = StringExtensions.ToString (runes.GetRange (start, end - start)); + + if (end > start && GetRuneWidth (str, tabWidth) <= width) + { + lines.Add (str); + start = end; + + if (runes [end].Value == ' ') + { + start++; + } + } + else + { + end++; + start = end; + } + } + } + else + { + while ((end = start + width) < runes.Count) + { + while (runes [end].Value != ' ' && end > start) + { + end--; + } + + if (end == start) + { + end = start + width; + } + + var zeroLength = 0; + + for (int i = end; i < runes.Count - start; i++) + { + Rune r = runes [i]; + + if (r.GetColumns () == 0) + { + zeroLength++; + } + else + { + break; + } + } + + lines.Add (StringExtensions.ToString (runes.GetRange (start, end - start + zeroLength))); + end += zeroLength; + start = end; + + if (runes [end].Value == ' ') + { + start++; + } + } + } + } + + int GetNextWhiteSpace (int from, int cWidth, out bool incomplete, int cLength = 0) + { + int lastFrom = from; + int to = from; + int length = cLength; + incomplete = false; + + while (length < cWidth && to < runes.Count) + { + Rune rune = runes [to]; + + if (IsHorizontalDirection (textDirection)) + { + length += rune.GetColumns (); + } + else + { + length++; + } + + if (length > cWidth) + { + if ((to >= runes.Count) || (length > 1 && cWidth <= 1)) + { + incomplete = true; + } + + return to; + } + + if (rune.Value == ' ') + { + if (length == cWidth) + { + return to + 1; + } + + if (length > cWidth) + { + return to; + } + + return GetNextWhiteSpace (to + 1, cWidth, out incomplete, length); + } + + if (rune.Value == '\t') + { + length += tabWidth + 1; + + if (length == tabWidth && tabWidth > cWidth) + { + return to + 1; + } + + if (length > cWidth && tabWidth > cWidth) + { + return to; + } + + return GetNextWhiteSpace (to + 1, cWidth, out incomplete, length); + } + + to++; + } + + if (cLength > 0 && to < runes.Count && runes [to].Value != ' ' && runes [to].Value != '\t') + { + return from; + } + + if (cLength > 0 && to < runes.Count && ((runes [to].Value == ' ') || (runes [to].Value == '\t'))) + { + return lastFrom; + } + + return to; + } + + if (start < text.GetRuneCount ()) + { + string str = ReplaceTABWithSpaces (StringExtensions.ToString (runes.GetRange (start, runes.Count - start)), tabWidth); + + if (IsVerticalDirection (textDirection) || preserveTrailingSpaces || (!preserveTrailingSpaces && str.GetColumns () <= width)) + { + lines.Add (str); + } + } + + return lines; + } + + /// Justifies text within a specified width. + /// The text to justify. + /// + /// The number of columns to clip the text to. Text longer than will be + /// clipped. + /// + /// Alignment. + /// The text direction. + /// The number of columns used for a tab. + /// Justified and clipped text. + public static string ClipAndJustify ( + string text, + int width, + TextAlignment talign, + TextDirection textDirection = TextDirection.LeftRight_TopBottom, + int tabWidth = 0 + ) + { + return ClipAndJustify (text, width, talign == TextAlignment.Justified, textDirection, tabWidth); + } + + /// Justifies text within a specified width. + /// The text to justify. + /// + /// The number of columns to clip the text to. Text longer than will be + /// clipped. + /// + /// Justify. + /// The text direction. + /// The number of columns used for a tab. + /// Justified and clipped text. + public static string ClipAndJustify ( + string text, + int width, + bool justify, + TextDirection textDirection = TextDirection.LeftRight_TopBottom, + int tabWidth = 0 + ) + { + if (width < 0) + { + throw new ArgumentOutOfRangeException ("Width cannot be negative."); + } + + if (string.IsNullOrEmpty (text)) + { + return text; + } + + text = ReplaceTABWithSpaces (text, tabWidth); + List runes = text.ToRuneList (); + int slen = runes.Count; + + if (slen > width) + { + if (IsHorizontalDirection (textDirection)) + { + return StringExtensions.ToString (runes.GetRange (0, GetLengthThatFits (text, width, tabWidth))); + } + + int zeroLength = runes.Sum (r => r.GetColumns () == 0 ? 1 : 0); + + return StringExtensions.ToString (runes.GetRange (0, width + zeroLength)); + } + + if (justify) + { + return Justify (text, width, ' ', textDirection, tabWidth); + } + + if (IsHorizontalDirection (textDirection) && GetRuneWidth (text, tabWidth) > width) + { + return StringExtensions.ToString (runes.GetRange (0, GetLengthThatFits (text, width, tabWidth))); + } + + return text; + } + + /// + /// Justifies the text to fill the width provided. Space will be added between words (demarked by spaces and tabs) to + /// make the text just fit width. Spaces will not be added to the ends. + /// + /// + /// + /// Character to replace whitespace and pad with. For debugging purposes. + /// The text direction. + /// The number of columns used for a tab. + /// The justified text. + public static string Justify ( + string text, + int width, + char spaceChar = ' ', + TextDirection textDirection = TextDirection.LeftRight_TopBottom, + int tabWidth = 0 + ) + { + if (width < 0) + { + throw new ArgumentOutOfRangeException ("Width cannot be negative."); + } + + if (string.IsNullOrEmpty (text)) + { + return text; + } + + text = ReplaceTABWithSpaces (text, tabWidth); + string [] words = text.Split (' '); + int textCount; + + if (IsHorizontalDirection (textDirection)) + { + textCount = words.Sum (arg => GetRuneWidth (arg, tabWidth)); + } + else + { + textCount = words.Sum (arg => arg.GetRuneCount ()); + } + + int spaces = words.Length > 1 ? (width - textCount) / (words.Length - 1) : 0; + int extras = words.Length > 1 ? (width - textCount) % (words.Length - 1) : 0; + + var s = new StringBuilder (); + + for (var w = 0; w < words.Length; w++) + { + string x = words [w]; + s.Append (x); + + if (w + 1 < words.Length) + { + for (var i = 0; i < spaces; i++) + { + s.Append (spaceChar); + } + } + + if (extras > 0) + { + for (var i = 0; i < 1; i++) + { + s.Append (spaceChar); + } + + extras--; + } + + if (w + 1 == words.Length - 1) + { + for (var i = 0; i < extras; i++) + { + s.Append (spaceChar); + } + } + } + + return s.ToString (); + } + + //static char [] whitespace = new char [] { ' ', '\t' }; + + /// + /// Reformats text into lines, applying text alignment and optionally wrapping text to new lines on word + /// boundaries. + /// + /// + /// The number of columns to constrain the text to for word wrapping and clipping. + /// Specifies how the text will be aligned horizontally. + /// + /// If , the text will be wrapped to new lines no longer than . If + /// , forces text to fit a single line. Line breaks are converted to spaces. The text will be + /// clipped to . + /// + /// + /// If trailing spaces at the end of wrapped lines will be preserved. If + /// , trailing spaces at the end of wrapped lines will be trimmed. + /// + /// The number of columns used for a tab. + /// The text direction. + /// If new lines are allowed. + /// A list of word wrapped lines. + /// + /// An empty string will result in one empty line. + /// If is 0, a single, empty line will be returned. + /// If is int.MaxValue, the text will be formatted to the maximum width possible. + /// + public static List Format ( + string text, + int width, + TextAlignment talign, + bool wordWrap, + bool preserveTrailingSpaces = false, + int tabWidth = 0, + TextDirection textDirection = TextDirection.LeftRight_TopBottom, + bool multiLine = false + ) + { + return Format (text, width, talign == TextAlignment.Justified, wordWrap, preserveTrailingSpaces, tabWidth, textDirection, multiLine); + } + + /// + /// Reformats text into lines, applying text alignment and optionally wrapping text to new lines on word + /// boundaries. + /// + /// + /// The number of columns to constrain the text to for word wrapping and clipping. + /// Specifies whether the text should be justified. + /// + /// If , the text will be wrapped to new lines no longer than . If + /// , forces text to fit a single line. Line breaks are converted to spaces. The text will be + /// clipped to . + /// + /// + /// If trailing spaces at the end of wrapped lines will be preserved. If + /// , trailing spaces at the end of wrapped lines will be trimmed. + /// + /// The number of columns used for a tab. + /// The text direction. + /// If new lines are allowed. + /// A list of word wrapped lines. + /// + /// An empty string will result in one empty line. + /// If is 0, a single, empty line will be returned. + /// If is int.MaxValue, the text will be formatted to the maximum width possible. + /// + public static List Format ( + string text, + int width, + bool justify, + bool wordWrap, + bool preserveTrailingSpaces = false, + int tabWidth = 0, + TextDirection textDirection = TextDirection.LeftRight_TopBottom, + bool multiLine = false + ) + { + if (width < 0) + { + throw new ArgumentOutOfRangeException ("width cannot be negative"); + } + + List lineResult = new (); + + if (string.IsNullOrEmpty (text) || (width == 0)) + { + lineResult.Add (string.Empty); + + return lineResult; + } + + if (!wordWrap) + { + text = ReplaceTABWithSpaces (text, tabWidth); + + if (multiLine) + { + string [] lines = null; + + if (text.Contains ("\r\n")) + { + lines = text.Split ("\r\n"); + } + else if (text.Contains ('\n')) + { + lines = text.Split ('\n'); + } + + if (lines == null) + { + lines = new [] { text }; + } + + foreach (string line in lines) + { + lineResult.Add (ClipAndJustify (line, width, justify, textDirection, tabWidth)); + } + + return lineResult; + } + + text = ReplaceCRLFWithSpace (text); + lineResult.Add (ClipAndJustify (text, width, justify, textDirection, tabWidth)); + + return lineResult; + } + + List runes = StripCRLF (text, true).ToRuneList (); + int runeCount = runes.Count; + var lp = 0; + + for (var i = 0; i < runeCount; i++) + { + Rune c = runes [i]; + + if (c.Value == '\n') + { + List wrappedLines = WordWrapText ( + StringExtensions.ToString (runes.GetRange (lp, i - lp)), + width, + preserveTrailingSpaces, + tabWidth, + textDirection); + + foreach (string line in wrappedLines) + { + lineResult.Add (ClipAndJustify (line, width, justify, textDirection, tabWidth)); + } + + if (wrappedLines.Count == 0) + { + lineResult.Add (string.Empty); + } + + lp = i + 1; + } + } + + foreach (string line in WordWrapText ( + StringExtensions.ToString (runes.GetRange (lp, runeCount - lp)), + width, + preserveTrailingSpaces, + tabWidth, + textDirection)) + { + lineResult.Add (ClipAndJustify (line, width, justify, textDirection, tabWidth)); + } + + return lineResult; + } + + /// Computes the number of lines needed to render the specified text given the width. + /// Number of lines. + /// Text, may contain newlines. + /// The minimum width for the text. + public static int MaxLines (string text, int width) + { + List result = Format (text, width, false, true); + + return result.Count; + } + + /// + /// Computes the maximum width needed to render the text (single line or multiple lines, word wrapped) given a number + /// of columns to constrain the text to. + /// + /// Width of the longest line after formatting the text constrained by . + /// Text, may contain newlines. + /// The number of columns to constrain the text to for formatting. + /// The number of columns used for a tab. + public static int MaxWidth (string text, int maxColumns, int tabWidth = 0) + { + List result = Format (text, maxColumns, false, true); + var max = 0; + + result.ForEach ( + s => + { + var m = 0; + s.ToRuneList ().ForEach (r => m += GetRuneWidth (r, tabWidth)); + + if (m > max) + { + max = m; + } + }); + + return max; + } + + /// + /// Returns the width of the widest line in the text, accounting for wide-glyphs (uses + /// ). if it contains newlines. + /// + /// Text, may contain newlines. + /// The number of columns used for a tab. + /// The length of the longest line. + public static int MaxWidthLine (string text, int tabWidth = 0) + { + List result = SplitNewLine (text); + + return result.Max (x => GetRuneWidth (x, tabWidth)); + } + + /// + /// Gets the maximum characters width from the list based on the and the + /// . + /// + /// The lines. + /// The start index. + /// The length. + /// The number of columns used for a tab. + /// The maximum characters width. + public static int GetSumMaxCharWidth (List lines, int startIndex = -1, int length = -1, int tabWidth = 0) + { + var max = 0; + + for (int i = startIndex == -1 ? 0 : startIndex; i < (length == -1 ? lines.Count : startIndex + length); i++) + { + string runes = lines [i]; + + if (runes.Length > 0) + { + max += runes.EnumerateRunes ().Max (r => GetRuneWidth (r, tabWidth)); + } + } + + return max; + } + + /// + /// Gets the maximum characters width from the text based on the and the + /// . + /// + /// The text. + /// The start index. + /// The length. + /// The number of columns used for a tab. + /// The maximum characters width. + public static int GetSumMaxCharWidth (string text, int startIndex = -1, int length = -1, int tabWidth = 0) + { + var max = 0; + Rune [] runes = text.ToRunes (); + + for (int i = startIndex == -1 ? 0 : startIndex; i < (length == -1 ? runes.Length : startIndex + length); i++) + { + max += GetRuneWidth (runes [i], tabWidth); + } + + return max; + } + + /// Gets the number of the Runes in a that will fit in . + /// The text. + /// The width. + /// The number of columns used for a tab. + /// The index of the text that fit the width. + public static int GetLengthThatFits (string text, int columns, int tabWidth = 0) { return GetLengthThatFits (text?.ToRuneList (), columns, tabWidth); } + + /// Gets the number of the Runes in a list of Runes that will fit in . + /// The list of runes. + /// The width. + /// The number of columns used for a tab. + /// The index of the last Rune in that fit in . + public static int GetLengthThatFits (List runes, int columns, int tabWidth = 0) + { + if ((runes == null) || (runes.Count == 0)) + { + return 0; + } + + var runesLength = 0; + var runeIdx = 0; + + for (; runeIdx < runes.Count; runeIdx++) + { + int runeWidth = GetRuneWidth (runes [runeIdx], tabWidth); + + if (runesLength + runeWidth > columns) + { + break; + } + + runesLength += runeWidth; + } + + return runeIdx; + } + + private static int GetRuneWidth (string str, int tabWidth) { return GetRuneWidth (str.EnumerateRunes ().ToList (), tabWidth); } + + private static int GetRuneWidth (List runes, int tabWidth) { return runes.Sum (r => GetRuneWidth (r, tabWidth)); } + + private static int GetRuneWidth (Rune rune, int tabWidth) + { + int runeWidth = rune.GetColumns (); + + if (rune.Value == '\t') + { + return tabWidth; + } + + if ((runeWidth < 0) || (runeWidth > 0)) + { + return Math.Max (runeWidth, 1); + } + + return runeWidth; + } + + /// Gets the index position from the list based on the . + /// The lines. + /// The width. + /// The number of columns used for a tab. + /// The index of the list that fit the width. + public static int GetMaxColsForWidth (List lines, int width, int tabWidth = 0) + { + var runesLength = 0; + var lineIdx = 0; + + for (; lineIdx < lines.Count; lineIdx++) + { + List runes = lines [lineIdx].ToRuneList (); + + int maxRruneWidth = runes.Count > 0 + ? runes.Max (r => GetRuneWidth (r, tabWidth)) + : 1; + + if (runesLength + maxRruneWidth > width) + { + break; + } + + runesLength += maxRruneWidth; + } + + return lineIdx; + } + + /// Calculates the rectangle required to hold a formatted string of text. + /// The x location of the rectangle + /// The y location of the rectangle + /// The text to measure + /// The text direction. + /// The number of columns used for a tab. + /// + public static Rect CalcRect (int x, int y, string text, TextDirection direction = TextDirection.LeftRight_TopBottom, int tabWidth = 0) + { + if (string.IsNullOrEmpty (text)) + { + return new Rect (new Point (x, y), Size.Empty); + } + + int w, h; + + if (IsHorizontalDirection (direction)) + { + var mw = 0; + var ml = 1; + + var cols = 0; + + foreach (Rune rune in text.EnumerateRunes ()) + { + if (rune.Value == '\n') + { + ml++; + + if (cols > mw) + { + mw = cols; + } + + cols = 0; + } + else if (rune.Value != '\r') + { + cols++; + var rw = 0; + + if (rune.Value == '\t') + { + rw += tabWidth - 1; + } + else + { + rw = rune.GetColumns (); + + if (rw > 0) + { + rw--; + } + else if (rw == 0) + { + cols--; + } + } + + cols += rw; + } + } + + if (cols > mw) + { + mw = cols; + } + + w = mw; + h = ml; + } + else + { + int vw = 1, cw = 1; + var vh = 0; + + var rows = 0; + + foreach (Rune rune in text.EnumerateRunes ()) + { + if (rune.Value == '\n') + { + vw++; + + if (rows > vh) + { + vh = rows; + } + + rows = 0; + cw = 1; + } + else if (rune.Value != '\r') + { + rows++; + var rw = 0; + + if (rune.Value == '\t') + { + rw += tabWidth - 1; + rows += rw; + } + else + { + rw = rune.GetColumns (); + + if (rw == 0) + { + rows--; + } + else if (cw < rw) + { + cw = rw; + vw++; + } + } + } + } + + if (rows > vh) + { + vh = rows; + } + + w = vw; + h = vh; + } + + return new Rect (x, y, w, h); + } + + /// Finds the HotKey and its location in text. + /// The text to look in. + /// The HotKey specifier (e.g. '_') to look for. + /// Outputs the Rune index into text. + /// Outputs the hotKey. if not found. + /// + /// If true the legacy behavior of identifying the first upper case character as the HotKey will be enabled. + /// Regardless of the value of this parameter, hotKeySpecifier takes precedence. Defaults to + /// . + /// + /// true if a HotKey was found; false otherwise. + public static bool FindHotKey (string text, Rune hotKeySpecifier, out int hotPos, out Key hotKey, bool firstUpperCase = false) + { + if (string.IsNullOrEmpty (text) || (hotKeySpecifier == (Rune)0xFFFF)) + { + hotPos = -1; + hotKey = KeyCode.Null; + + return false; + } + + var hot_key = (Rune)0; + int hot_pos = -1; + + // Use first hot_key char passed into 'hotKey'. + // TODO: Ignore hot_key of two are provided + // TODO: Do not support non-alphanumeric chars that can't be typed + var i = 0; + + foreach (Rune c in text.EnumerateRunes ()) + { + if ((char)c.Value != 0xFFFD) + { + if (c == hotKeySpecifier) + { + hot_pos = i; + } + else if (hot_pos > -1) + { + hot_key = c; + + break; + } + } + + i++; + } + + // Legacy support - use first upper case char if the specifier was not found + if (hot_pos == -1 && firstUpperCase) + { + i = 0; + + foreach (Rune c in text.EnumerateRunes ()) + { + if ((char)c.Value != 0xFFFD) + { + if (Rune.IsUpper (c)) + { + hot_key = c; + hot_pos = i; + + break; + } + } + + i++; + } + } + + if (hot_key != (Rune)0 && hot_pos != -1) + { + hotPos = hot_pos; + + var newHotKey = (KeyCode)hot_key.Value; + + if (newHotKey != KeyCode.Null && !((newHotKey == KeyCode.Space) || Rune.IsControl (hot_key))) + { + if ((newHotKey & ~KeyCode.Space) is >= KeyCode.A and <= KeyCode.Z) + { + newHotKey &= ~KeyCode.Space; + } + + hotKey = newHotKey; + + return true; + } + } + + hotPos = -1; + hotKey = KeyCode.Null; + + return false; + } + + /// + /// Replaces the Rune at the index specified by the hotPos parameter with a tag identifying it as the + /// hotkey. + /// + /// The text to tag the hotkey in. + /// The Rune index of the hotkey in text. + /// The text with the hotkey tagged. + /// The returned string will not render correctly without first un-doing the tag. To undo the tag, search for + public string ReplaceHotKeyWithTag (string text, int hotPos) + { + // Set the high bit + List runes = text.ToRuneList (); + + if (Rune.IsLetterOrDigit (runes [hotPos])) + { + runes [hotPos] = new Rune ((uint)runes [hotPos].Value); + } + + return StringExtensions.ToString (runes); + } + + /// Removes the hotkey specifier from text. + /// The text to manipulate. + /// The hot-key specifier (e.g. '_') to look for. + /// Returns the position of the hot-key in the text. -1 if not found. + /// The input text with the hotkey specifier ('_') removed. + public static string RemoveHotKeySpecifier (string text, int hotPos, Rune hotKeySpecifier) + { + if (string.IsNullOrEmpty (text)) + { + return text; + } + + // Scan + var start = string.Empty; + var i = 0; + + foreach (Rune c in text) + { + if (c == hotKeySpecifier && i == hotPos) + { + i++; + + continue; + } + + start += c; + i++; + } + + return start; + } + + #endregion // Static Members } diff --git a/Terminal.Gui/View/Layout/PosDim.cs b/Terminal.Gui/View/Layout/PosDim.cs index edae232b6..3b556a292 100644 --- a/Terminal.Gui/View/Layout/PosDim.cs +++ b/Terminal.Gui/View/Layout/PosDim.cs @@ -1,981 +1,993 @@ -using System; - -namespace Terminal.Gui; +namespace Terminal.Gui; /// -/// Describes the position of a which can be an absolute value, a percentage, centered, or -/// Describes the position of a which can be an absolute value, a percentage, centered, or -/// relative to the ending dimension. Integer values are implicitly convertible to -/// an absolute . These objects are created using the static methods Percent, -/// AnchorEnd, and Center. The objects can be combined with the addition and -/// AnchorEnd, and Center. The objects can be combined with the addition and -/// subtraction operators. +/// Describes the position of a which can be an absolute value, a percentage, centered, or Describes +/// the position of a which can be an absolute value, a percentage, centered, or relative to the +/// ending dimension. Integer values are implicitly convertible to an absolute . These objects are +/// created using the static methods Percent, AnchorEnd, and Center. The objects can be combined with +/// the addition and AnchorEnd, and Center. The objects can be combined with the addition and +/// subtraction operators. /// /// -/// -/// Use the objects on the X or Y properties of a view to control the position. -/// -/// -/// These can be used to set the absolute position, when merely assigning an -/// integer value (via the implicit integer to conversion), and they can be combined -/// to produce more useful layouts, like: Pos.Center - 3, which would shift the position -/// of the 3 characters to the left after centering for example. -/// -/// +/// Use the objects on the X or Y properties of a view to control the position. +/// +/// These can be used to set the absolute position, when merely assigning an integer value (via the implicit +/// integer to conversion), and they can be combined to produce more useful layouts, like: +/// Pos.Center - 3, which would shift the position of the 3 characters to the left after +/// centering for example. +/// +/// /// Reference coordinates of another view by using the methods Left(View), Right(View), Bottom(View), Top(View). -/// The X(View) and Y(View) are -/// aliases to Left(View) and Top(View) respectively. -/// -/// -/// -/// -/// Pos Object -/// Description -/// -/// -/// -/// -/// -/// -/// Creates a object that computes the position by executing the provided -/// function. The function will be called every time the position is needed. -/// -/// -/// -/// -/// -/// -/// -/// Creates a object that is a percentage of the width or height of the -/// SuperView. -/// -/// -/// -/// -/// -/// -/// -/// Creates a object that is anchored to the end (right side or bottom) -/// of the dimension, -/// useful to flush the layout from the right or bottom. -/// -/// -/// -/// -/// -/// -/// -/// Creates a object that can be used to center the . -/// -/// -/// -/// -/// -/// -/// -/// Creates a object that is an absolute position based on the specified -/// integer value. -/// -/// -/// -/// -/// -/// -/// -/// Creates a object that tracks the Left (X) position of the specified -/// . -/// -/// -/// -/// -/// -/// -/// -/// Creates a object that tracks the Left (X) position of the specified -/// . -/// -/// -/// -/// -/// -/// -/// -/// Creates a object that tracks the Top (Y) position of the specified -/// . -/// -/// -/// -/// -/// -/// -/// -/// Creates a object that tracks the Top (Y) position of the specified -/// . -/// -/// -/// -/// -/// -/// -/// -/// Creates a object that tracks the Right (X+Width) coordinate of the -/// specified . -/// -/// -/// -/// -/// -/// -/// -/// Creates a object that tracks the Bottom (Y+Height) coordinate of the -/// specified -/// -/// -/// -/// -/// -/// -/// Use the objects on the X or Y properties of a view to control the position. -/// -/// -/// These can be used to set the absolute position, when merely assigning an -/// integer value (via the implicit integer to conversion), and they can be combined -/// to produce more useful layouts, like: Pos.Center - 3, which would shift the position -/// of the 3 characters to the left after centering for example. -/// -/// +/// The X(View) and Y(View) are aliases to Left(View) and Top(View) respectively. +/// +/// +/// +/// +/// Pos Object Description +/// +/// +/// +/// +/// +/// +/// Creates a object that computes the position by executing the provided function. +/// The function will be called every time the position is needed. +/// +/// +/// +/// +/// +/// +/// +/// Creates a object that is a percentage of the width or height of the +/// SuperView. +/// +/// +/// +/// +/// +/// +/// +/// Creates a object that is anchored to the end (right side or bottom) of the +/// dimension, useful to flush the layout from the right or bottom. +/// +/// +/// +/// +/// +/// +/// Creates a object that can be used to center the . +/// +/// +/// +/// +/// +/// +/// Creates a object that is an absolute position based on the specified +/// integer value. +/// +/// +/// +/// +/// +/// +/// +/// Creates a object that tracks the Left (X) position of the specified +/// . +/// +/// +/// +/// +/// +/// +/// +/// Creates a object that tracks the Left (X) position of the specified +/// . +/// +/// +/// +/// +/// +/// +/// +/// Creates a object that tracks the Top (Y) position of the specified +/// . +/// +/// +/// +/// +/// +/// +/// +/// Creates a object that tracks the Top (Y) position of the specified +/// . +/// +/// +/// +/// +/// +/// +/// +/// Creates a object that tracks the Right (X+Width) coordinate of the +/// specified . +/// +/// +/// +/// +/// +/// +/// +/// Creates a object that tracks the Bottom (Y+Height) coordinate of the +/// specified +/// +/// +/// +/// +/// Use the objects on the X or Y properties of a view to control the position. +/// +/// These can be used to set the absolute position, when merely assigning an integer value (via the implicit +/// integer to conversion), and they can be combined to produce more useful layouts, like: +/// Pos.Center - 3, which would shift the position of the 3 characters to the left after +/// centering for example. +/// +/// /// Reference coordinates of another view by using the methods Left(View), Right(View), Bottom(View), Top(View). -/// The X(View) and Y(View) are -/// aliases to Left(View) and Top(View) respectively. -/// -/// -/// -/// -/// Pos Object -/// Description -/// -/// -/// -/// -/// -/// -/// Creates a object that computes the position by executing the provided -/// function. The function will be called every time the position is needed. -/// -/// -/// -/// -/// -/// -/// -/// Creates a object that is a percentage of the width or height of the -/// SuperView. -/// -/// -/// -/// -/// -/// -/// -/// Creates a object that is anchored to the end (right side or bottom) -/// of the dimension, -/// useful to flush the layout from the right or bottom. -/// -/// -/// -/// -/// -/// -/// -/// Creates a object that can be used to center the . -/// -/// -/// -/// -/// -/// -/// -/// Creates a object that is an absolute position based on the specified -/// integer value. -/// -/// -/// -/// -/// -/// -/// -/// Creates a object that tracks the Left (X) position of the specified -/// . -/// -/// -/// -/// -/// -/// -/// -/// Creates a object that tracks the Left (X) position of the specified -/// . -/// -/// -/// -/// -/// -/// -/// -/// Creates a object that tracks the Top (Y) position of the specified -/// . -/// -/// -/// -/// -/// -/// -/// -/// Creates a object that tracks the Top (Y) position of the specified -/// . -/// -/// -/// -/// -/// -/// -/// -/// Creates a object that tracks the Right (X+Width) coordinate of the -/// specified . -/// -/// -/// -/// -/// -/// -/// -/// Creates a object that tracks the Bottom (Y+Height) coordinate of the -/// specified -/// -/// -/// -/// -/// +/// The X(View) and Y(View) are aliases to Left(View) and Top(View) respectively. +/// +/// +/// +/// +/// Pos Object Description +/// +/// +/// +/// +/// +/// +/// Creates a object that computes the position by executing the provided function. +/// The function will be called every time the position is needed. +/// +/// +/// +/// +/// +/// +/// +/// Creates a object that is a percentage of the width or height of the +/// SuperView. +/// +/// +/// +/// +/// +/// +/// +/// Creates a object that is anchored to the end (right side or bottom) of the +/// dimension, useful to flush the layout from the right or bottom. +/// +/// +/// +/// +/// +/// +/// Creates a object that can be used to center the . +/// +/// +/// +/// +/// +/// +/// Creates a object that is an absolute position based on the specified +/// integer value. +/// +/// +/// +/// +/// +/// +/// +/// Creates a object that tracks the Left (X) position of the specified +/// . +/// +/// +/// +/// +/// +/// +/// +/// Creates a object that tracks the Left (X) position of the specified +/// . +/// +/// +/// +/// +/// +/// +/// +/// Creates a object that tracks the Top (Y) position of the specified +/// . +/// +/// +/// +/// +/// +/// +/// +/// Creates a object that tracks the Top (Y) position of the specified +/// . +/// +/// +/// +/// +/// +/// +/// +/// Creates a object that tracks the Right (X+Width) coordinate of the +/// specified . +/// +/// +/// +/// +/// +/// +/// +/// Creates a object that tracks the Bottom (Y+Height) coordinate of the +/// specified +/// +/// +/// +/// /// -public class Pos { - internal virtual int Anchor (int width) => 0; +public class Pos +{ + /// + /// Creates a object that is anchored to the end (right side or bottom) of the dimension, useful to + /// flush the layout from the right or bottom. + /// + /// The object anchored to the end (the bottom or the right side). + /// The view will be shifted left or up by the amount specified. + /// + /// This sample shows how align a to the bottom-right of a . + /// + /// // See Issue #502 + /// anchorButton.X = Pos.AnchorEnd () - (Pos.Right (anchorButton) - Pos.Left (anchorButton)); + /// anchorButton.Y = Pos.AnchorEnd (1); + /// + /// + public static Pos AnchorEnd (int offset = 0) + { + if (offset < 0) + { + throw new ArgumentException (@"Must be positive", nameof (offset)); + } - /// - /// Creates a object that computes the position by executing the provided function. The function will be - /// called every time the position is needed. - /// - /// The function to be executed. - /// The returned from the function. - public static Pos Function (Func function) => new PosFunc (function); + return new PosAnchorEnd (offset); + } - /// - /// Creates a percentage object - /// - /// The percent object. - /// A value between 0 and 100 representing the percentage. - /// - /// This creates a that is centered horizontally, is 50% of the way down, - /// is 30% the height, and is 80% the width of the it added to. - /// - /// var textView = new TextView () { - /// X = Pos.Center (), - /// Y = Pos.Percent (50), - /// Width = Dim.Percent (80), - /// Height = Dim.Percent (30), - /// }; - /// - /// - public static Pos Percent (float n) - { - if (n is < 0 or > 100) { - throw new ArgumentException ("Percent value must be between 0 and 100"); - } + /// Creates a object that is an absolute position based on the specified integer value. + /// The Absolute . + /// The value to convert to the . + public static Pos At (int n) { return new PosAbsolute (n); } - return new PosFactor (n / 100); - } + /// + /// Creates a object that tracks the Bottom (Y+Height) coordinate of the specified + /// + /// + /// The that depends on the other view. + /// The that will be tracked. + public static Pos Bottom (View view) { return new PosView (view, 3); } - /// - /// Creates a object that is anchored to the end (right side or bottom) of the dimension, - /// useful to flush the layout from the right or bottom. - /// - /// The object anchored to the end (the bottom or the right side). - /// The view will be shifted left or up by the amount specified. - /// - /// This sample shows how align a to the bottom-right of a . - /// - /// // See Issue #502 - /// anchorButton.X = Pos.AnchorEnd () - (Pos.Right (anchorButton) - Pos.Left (anchorButton)); - /// anchorButton.Y = Pos.AnchorEnd (1); - /// - /// - public static Pos AnchorEnd (int offset = 0) - { - if (offset < 0) { - throw new ArgumentException (@"Must be positive", nameof (offset)); - } + /// Creates a object that can be used to center the . + /// The center Pos. + /// + /// This creates a that is centered horizontally, is 50% of the way down, is 30% the height, and + /// is 80% the width of the it added to. + /// + /// var textView = new TextView () { + /// X = Pos.Center (), + /// Y = Pos.Percent (50), + /// Width = Dim.Percent (80), + /// Height = Dim.Percent (30), + /// }; + /// + /// + public static Pos Center () { return new PosCenter (); } - return new PosAnchorEnd (offset); - } + /// Determines whether the specified object is equal to the current object. + /// The object to compare with the current object. + /// + /// if the specified object is equal to the current object; otherwise, + /// . + /// + public override bool Equals (object other) { return other is Pos abs && abs == this; } - /// - /// Creates a object that can be used to center the . - /// - /// The center Pos. - /// - /// This creates a that is centered horizontally, is 50% of the way down, - /// is 30% the height, and is 80% the width of the it added to. - /// - /// var textView = new TextView () { - /// X = Pos.Center (), - /// Y = Pos.Percent (50), - /// Width = Dim.Percent (80), - /// Height = Dim.Percent (30), - /// }; - /// - /// - public static Pos Center () => new PosCenter (); + /// + /// Creates a object that computes the position by executing the provided function. The function will + /// be called every time the position is needed. + /// + /// The function to be executed. + /// The returned from the function. + public static Pos Function (Func function) { return new PosFunc (function); } - /// - /// Creates a object that is an absolute position based on the specified integer value. - /// - /// The Absolute . - /// The value to convert to the . - public static Pos At (int n) => new PosAbsolute (n); + /// Serves as the default hash function. + /// A hash code for the current object. + public override int GetHashCode () { return Anchor (0).GetHashCode (); } - /// - /// Creates an Absolute from the specified integer value. - /// - /// The Absolute . - /// The value to convert to the . - public static implicit operator Pos (int n) => new PosAbsolute (n); + /// Creates a object that tracks the Left (X) position of the specified . + /// The that depends on the other view. + /// The that will be tracked. + public static Pos Left (View view) { return new PosView (view, 0); } - /// - /// Adds a to a , yielding a new . - /// - /// The first to add. - /// The second to add. - /// The that is the sum of the values of left and right. - public static Pos operator + (Pos left, Pos right) - { - if (left is PosAbsolute && right is PosAbsolute) { - return new PosAbsolute (left.Anchor (0) + right.Anchor (0)); - } - var newPos = new PosCombine (true, left, right); - SetPosCombine (left, newPos); - return newPos; - } + /// Adds a to a , yielding a new . + /// The first to add. + /// The second to add. + /// The that is the sum of the values of left and right. + public static Pos operator + (Pos left, Pos right) + { + if (left is PosAbsolute && right is PosAbsolute) + { + return new PosAbsolute (left.Anchor (0) + right.Anchor (0)); + } - /// - /// Subtracts a from a , yielding a new . - /// - /// The to subtract from (the minuend). - /// The to subtract (the subtrahend). - /// The that is the left minus right. - public static Pos operator - (Pos left, Pos right) - { - if (left is PosAbsolute && right is PosAbsolute) { - return new PosAbsolute (left.Anchor (0) - right.Anchor (0)); - } - var newPos = new PosCombine (false, left, right); - SetPosCombine (left, newPos); - return newPos; - } + var newPos = new PosCombine (true, left, right); + SetPosCombine (left, newPos); - static void SetPosCombine (Pos left, PosCombine newPos) - { - var view = left as PosView; - if (view != null) { - view.Target.SetNeedsLayout (); - } - } + return newPos; + } - /// - /// Creates a object that tracks the Left (X) position of the specified . - /// - /// The that depends on the other view. - /// The that will be tracked. - public static Pos Left (View view) => new PosView (view, 0); + /// Creates an Absolute from the specified integer value. + /// The Absolute . + /// The value to convert to the . + public static implicit operator Pos (int n) { return new PosAbsolute (n); } - /// - /// Creates a object that tracks the Left (X) position of the specified . - /// - /// The that depends on the other view. - /// The that will be tracked. - public static Pos X (View view) => new PosView (view, 0); + /// + /// Subtracts a from a , yielding a new + /// . + /// + /// The to subtract from (the minuend). + /// The to subtract (the subtrahend). + /// The that is the left minus right. + public static Pos operator - (Pos left, Pos right) + { + if (left is PosAbsolute && right is PosAbsolute) + { + return new PosAbsolute (left.Anchor (0) - right.Anchor (0)); + } - /// - /// Creates a object that tracks the Top (Y) position of the specified . - /// - /// The that depends on the other view. - /// The that will be tracked. - public static Pos Top (View view) => new PosView (view, 1); + var newPos = new PosCombine (false, left, right); + SetPosCombine (left, newPos); - /// - /// Creates a object that tracks the Top (Y) position of the specified . - /// - /// The that depends on the other view. - /// The that will be tracked. - public static Pos Y (View view) => new PosView (view, 1); + return newPos; + } - /// - /// Creates a object that tracks the Right (X+Width) coordinate of the specified . - /// - /// The that depends on the other view. - /// The that will be tracked. - public static Pos Right (View view) => new PosView (view, 2); + /// Creates a percentage object + /// The percent object. + /// A value between 0 and 100 representing the percentage. + /// + /// This creates a that is centered horizontally, is 50% of the way down, is 30% the height, and + /// is 80% the width of the it added to. + /// + /// var textView = new TextView () { + /// X = Pos.Center (), + /// Y = Pos.Percent (50), + /// Width = Dim.Percent (80), + /// Height = Dim.Percent (30), + /// }; + /// + /// + public static Pos Percent (float n) + { + if (n is < 0 or > 100) + { + throw new ArgumentException ("Percent value must be between 0 and 100"); + } - /// - /// Creates a object that tracks the Bottom (Y+Height) coordinate of the specified - /// - /// The that depends on the other view. - /// The that will be tracked. - public static Pos Bottom (View view) => new PosView (view, 3); + return new PosFactor (n / 100); + } - /// Serves as the default hash function. - /// A hash code for the current object. - public override int GetHashCode () => Anchor (0).GetHashCode (); + /// + /// Creates a object that tracks the Right (X+Width) coordinate of the specified + /// . + /// + /// The that depends on the other view. + /// The that will be tracked. + public static Pos Right (View view) { return new PosView (view, 2); } - /// Determines whether the specified object is equal to the current object. - /// The object to compare with the current object. - /// - /// if the specified object is equal to the current object; otherwise, . - /// - public override bool Equals (object other) => other is Pos abs && abs == this; + /// Creates a object that tracks the Top (Y) position of the specified . + /// The that depends on the other view. + /// The that will be tracked. + public static Pos Top (View view) { return new PosView (view, 1); } - internal class PosFactor : Pos { - readonly float _factor; + /// Creates a object that tracks the Left (X) position of the specified . + /// The that depends on the other view. + /// The that will be tracked. + public static Pos X (View view) { return new PosView (view, 0); } - public PosFactor (float n) => _factor = n; + /// Creates a object that tracks the Top (Y) position of the specified . + /// The that depends on the other view. + /// The that will be tracked. + public static Pos Y (View view) { return new PosView (view, 1); } - internal override int Anchor (int width) => (int)(width * _factor); + internal virtual int Anchor (int width) { return 0; } - public override string ToString () => $"Factor({_factor})"; + private static void SetPosCombine (Pos left, PosCombine newPos) + { + var view = left as PosView; - public override int GetHashCode () => _factor.GetHashCode (); + if (view != null) + { + view.Target.SetNeedsLayout (); + } + } - public override bool Equals (object other) => other is PosFactor f && f._factor == _factor; - } + internal class PosAbsolute : Pos + { + private readonly int _n; + public PosAbsolute (int n) { _n = n; } - // Helper class to provide dynamic value by the execution of a function that returns an integer. - internal class PosFunc : Pos { - readonly Func _function; + public override bool Equals (object other) { return other is PosAbsolute abs && abs._n == _n; } - public PosFunc (Func n) => _function = n; + public override int GetHashCode () { return _n.GetHashCode (); } - internal override int Anchor (int width) => _function (); + public override string ToString () { return $"Absolute({_n})"; } - public override string ToString () => $"PosFunc({_function ()})"; + internal override int Anchor (int width) { return _n; } + } - public override int GetHashCode () => _function.GetHashCode (); + internal class PosAnchorEnd : Pos + { + private readonly int _offset; - public override bool Equals (object other) => other is PosFunc f && f._function () == _function (); - } + public PosAnchorEnd (int offset) { _offset = offset; } - internal class PosAnchorEnd : Pos { - readonly int _offset; + public override bool Equals (object other) { return other is PosAnchorEnd anchorEnd && anchorEnd._offset == _offset; } - public PosAnchorEnd (int offset) => _offset = offset; + public override int GetHashCode () { return _offset.GetHashCode (); } - internal override int Anchor (int width) => width - _offset; + public override string ToString () { return $"AnchorEnd({_offset})"; } - public override string ToString () => $"AnchorEnd({_offset})"; + internal override int Anchor (int width) { return width - _offset; } + } - public override int GetHashCode () => _offset.GetHashCode (); + internal class PosCenter : Pos + { + public override string ToString () { return "Center"; } - public override bool Equals (object other) => other is PosAnchorEnd anchorEnd && anchorEnd._offset == _offset; - } + internal override int Anchor (int width) { return width / 2; } + } - internal class PosAbsolute : Pos { - readonly int _n; - public PosAbsolute (int n) => _n = n; + internal class PosCombine : Pos + { + internal bool _add; + internal Pos _left, _right; - public override string ToString () => $"Absolute({_n})"; + public PosCombine (bool add, Pos left, Pos right) + { + _left = left; + _right = right; + _add = add; + } - internal override int Anchor (int width) => _n; + public override string ToString () { return $"Combine({_left}{(_add ? '+' : '-')}{_right})"; } - public override int GetHashCode () => _n.GetHashCode (); + internal override int Anchor (int width) + { + int la = _left.Anchor (width); + int ra = _right.Anchor (width); - public override bool Equals (object other) => other is PosAbsolute abs && abs._n == _n; - } + if (_add) + { + return la + ra; + } - internal class PosCenter : Pos { - internal override int Anchor (int width) => width / 2; + return la - ra; + } + } - public override string ToString () => "Center"; - } + internal class PosFactor : Pos + { + private readonly float _factor; - internal class PosCombine : Pos { - internal bool _add; - internal Pos _left, _right; + public PosFactor (float n) { _factor = n; } - public PosCombine (bool add, Pos left, Pos right) - { - _left = left; - _right = right; - _add = add; - } + public override bool Equals (object other) { return other is PosFactor f && f._factor == _factor; } - internal override int Anchor (int width) - { - var la = _left.Anchor (width); - var ra = _right.Anchor (width); - if (_add) { - return la + ra; - } - return la - ra; - } + public override int GetHashCode () { return _factor.GetHashCode (); } - public override string ToString () => $"Combine({_left}{(_add ? '+' : '-')}{_right})"; - } + public override string ToString () { return $"Factor({_factor})"; } - internal class PosView : Pos { - public readonly View Target; - readonly int side; + internal override int Anchor (int width) { return (int)(width * _factor); } + } - public PosView (View view, int side) - { - Target = view; - this.side = side; - } + // Helper class to provide dynamic value by the execution of a function that returns an integer. + internal class PosFunc : Pos + { + private readonly Func _function; - internal override int Anchor (int width) - { - switch (side) { - case 0: return Target.Frame.X; - case 1: return Target.Frame.Y; - case 2: return Target.Frame.Right; - case 3: return Target.Frame.Bottom; - default: - return 0; - } - } + public PosFunc (Func n) { _function = n; } - public override string ToString () - { - string tside; - switch (side) { - case 0: - tside = "x"; - break; - case 1: - tside = "y"; - break; - case 2: - tside = "right"; - break; - case 3: - tside = "bottom"; - break; - default: - tside = "unknown"; - break; - } - if (Target == null) { - throw new NullReferenceException (nameof (Target)); - } - return $"View(side={tside},target={Target})"; - } + public override bool Equals (object other) { return other is PosFunc f && f._function () == _function (); } - public override int GetHashCode () => Target.GetHashCode (); + public override int GetHashCode () { return _function.GetHashCode (); } - public override bool Equals (object other) => other is PosView abs && abs.Target == Target; - } + public override string ToString () { return $"PosFunc({_function ()})"; } + + internal override int Anchor (int width) { return _function (); } + } + + internal class PosView : Pos + { + public readonly View Target; + private readonly int side; + + public PosView (View view, int side) + { + Target = view; + this.side = side; + } + + public override bool Equals (object other) { return other is PosView abs && abs.Target == Target; } + + public override int GetHashCode () { return Target.GetHashCode (); } + + public override string ToString () + { + string tside; + + switch (side) + { + case 0: + tside = "x"; + + break; + case 1: + tside = "y"; + + break; + case 2: + tside = "right"; + + break; + case 3: + tside = "bottom"; + + break; + default: + tside = "unknown"; + + break; + } + + if (Target == null) + { + throw new NullReferenceException (nameof (Target)); + } + + return $"View(side={tside},target={Target})"; + } + + internal override int Anchor (int width) + { + switch (side) + { + case 0: return Target.Frame.X; + case 1: return Target.Frame.Y; + case 2: return Target.Frame.Right; + case 3: return Target.Frame.Bottom; + default: + return 0; + } + } + } } /// -/// +/// /// A Dim object describes the dimensions of a . Dim is the type of the -/// and -/// properties of . Dim objects enable Computed Layout (see -/// ) -/// to automatically manage the dimensions of a view. -/// -/// +/// and properties of . Dim objects enable Computed Layout (see +/// ) to automatically manage the dimensions of a view. +/// +/// /// Integer values are implicitly convertible to an absolute . These objects are created using the -/// static methods described below. -/// The objects can be combined with the addition and subtraction operators. -/// +/// static methods described below. The objects can be combined with the addition and subtraction +/// operators. +/// /// /// -/// -/// -/// -/// Dim Object -/// Description -/// -/// -/// -/// -/// -/// -/// Creates a object that automatically sizes the view to fit -/// all of the view's SubViews. -/// -/// -/// -/// -/// -/// -/// -/// Creates a object that computes the dimension by executing the -/// provided function. The function will be called every time the dimension is needed. -/// -/// -/// -/// -/// -/// -/// -/// Creates a object that is a percentage of the width or height of the -/// SuperView. -/// -/// -/// -/// -/// -/// -/// -/// Creates a object that fills the dimension, leaving the specified -/// number of columns for a margin. -/// -/// -/// -/// -/// -/// -/// -/// Creates a object that tracks the Width of the specified -/// . -/// -/// -/// -/// -/// -/// -/// -/// Creates a object that tracks the Height of the specified -/// . -/// -/// -/// -/// -/// -/// +/// +/// +/// +/// Dim Object Description +/// +/// +/// +/// +/// +/// +/// Creates a object that automatically sizes the view to fit all of the +/// view's SubViews. +/// +/// +/// +/// +/// +/// +/// +/// Creates a object that computes the dimension by executing the provided function. +/// The function will be called every time the dimension is needed. +/// +/// +/// +/// +/// +/// +/// +/// Creates a object that is a percentage of the width or height of the +/// SuperView. +/// +/// +/// +/// +/// +/// +/// +/// Creates a object that fills the dimension, leaving the specified number +/// of columns for a margin. +/// +/// +/// +/// +/// +/// +/// +/// Creates a object that tracks the Width of the specified +/// . +/// +/// +/// +/// +/// +/// +/// +/// Creates a object that tracks the Height of the specified +/// . +/// +/// +/// +/// +/// /// -public class Dim { - internal virtual int Anchor (int width) => 0; +public class Dim +{ + /// Specifies how will compute the dimension. + public enum DimAutoStyle + { + /// + /// The dimension will be computed using both the view's and . The + /// larger of the corresponding text dimension or Subview in with the largest corresponding + /// position plus dimension will determine the dimension. + /// + Auto, - /// - /// Specifies how will compute the dimension. - /// - public enum DimAutoStyle { - /// - /// The dimension will be computed using both the view's and - /// . - /// The larger of the corresponding text dimension or Subview in - /// with the largest corresponding position plus dimension will determine the dimension. - /// - Auto, + /// + /// The Subview in with the largest corresponding position plus dimension will determine + /// the dimension. The corresponding dimension of the view's will be ignored. + /// + Subviews, - /// - /// The Subview in with the largest corresponding position plus dimension - /// will determine the dimension. - /// The corresponding dimension of the view's will be ignored. - /// - Subviews, + /// + /// The corresponding dimension of the view's , formatted using the + /// settings, will be used to determine the dimension. The corresponding dimensions of + /// the will be ignored. + /// + Text + } - /// - /// The corresponding dimension of the view's , formatted using the settings, - /// will be used to determine the dimension. - /// The corresponding dimensions of the will be ignored. - /// - Text - } + /// + /// Creates a object that automatically sizes the view to fit all of the view's SubViews and/or + /// Text. + /// + /// + /// This initializes a with two SubViews. The view will be automatically sized to fit the two + /// SubViews. + /// + /// var button = new Button () { Text = "Click Me!", X = 1, Y = 1, Width = 10, Height = 1 }; + /// var textField = new TextField { Text = "Type here", X = 1, Y = 2, Width = 20, Height = 1 }; + /// var view = new Window () { Title = "MyWindow", X = 0, Y = 0, Width = Dim.AutoSize (), Height = Dim.AutoSize () }; + /// view.Add (button, textField); + /// + /// + /// The AutoSize object. + /// + /// Specifies how will compute the dimension. The default is + /// . + /// + /// Specifies the minimum dimension that view will be automatically sized to. + /// Specifies the maximum dimension that view will be automatically sized to. NOT CURRENTLY SUPPORTED. + public static Dim Auto (DimAutoStyle style = DimAutoStyle.Auto, Dim min = null, Dim max = null) + { + if (max != null) + { + throw new NotImplementedException (@"max is not implemented"); + } - /// - /// Creates a object that automatically sizes the view to fit all of the view's SubViews and/or Text. - /// - /// - /// This initializes a with two SubViews. The view will be automatically sized to fit the two - /// SubViews. - /// - /// var button = new Button () { Text = "Click Me!", X = 1, Y = 1, Width = 10, Height = 1 }; - /// var textField = new TextField { Text = "Type here", X = 1, Y = 2, Width = 20, Height = 1 }; - /// var view = new Window () { Title = "MyWindow", X = 0, Y = 0, Width = Dim.AutoSize (), Height = Dim.AutoSize () }; - /// view.Add (button, textField); - /// - /// - /// The AutoSize object. - /// - /// Specifies how will compute the dimension. The default is . - /// - /// Specifies the minimum dimension that view will be automatically sized to. - /// Specifies the maximum dimension that view will be automatically sized to. NOT CURRENTLY SUPPORTED. - public static Dim Auto (DimAutoStyle style = DimAutoStyle.Auto, Dim min = null, Dim max = null) - { - if (max != null) { - throw new NotImplementedException (@"max is not implemented"); - } - return new DimAuto (style, min, max); - } - - /// - /// Creates a function object that computes the dimension by executing the provided function. - /// The function will be called every time the dimension is needed. - /// - /// The function to be executed. - /// The returned from the function. - public static Dim Function (Func function) => new DimFunc (function); + return new DimAuto (style, min, max); + } - /// - /// Creates a percentage object that is a percentage of the width or height of the SuperView. - /// - /// The percent object. - /// A value between 0 and 100 representing the percentage. - /// - /// If true the Percent is computed based on the remaining space after the X/Y anchor positions. - /// If false is computed based on the whole original space. - /// - /// - /// This initializes a that is centered horizontally, is 50% of the way down, - /// is 30% the height, and is 80% the width of the it added to. - /// - /// var textView = new TextView () { - /// X = Pos.Center (), - /// Y = Pos.Percent (50), - /// Width = Dim.Percent (80), - /// Height = Dim.Percent (30), - /// }; - /// - /// - public static Dim Percent (float n, bool r = false) - { - if (n is < 0 or > 100) { - throw new ArgumentException ("Percent value must be between 0 and 100"); - } + /// Determines whether the specified object is equal to the current object. + /// The object to compare with the current object. + /// + /// if the specified object is equal to the current object; otherwise, + /// . + /// + public override bool Equals (object other) { return other is Dim abs && abs == this; } - return new DimFactor (n / 100, r); - } + /// + /// Creates a object that fills the dimension, leaving the specified number of columns for a + /// margin. + /// + /// The Fill dimension. + /// Margin to use. + public static Dim Fill (int margin = 0) { return new DimFill (margin); } - /// - /// Creates a object that fills the dimension, leaving the specified number of columns for a margin. - /// - /// The Fill dimension. - /// Margin to use. - public static Dim Fill (int margin = 0) => new DimFill (margin); + /// + /// Creates a function object that computes the dimension by executing the provided function. The + /// function will be called every time the dimension is needed. + /// + /// The function to be executed. + /// The returned from the function. + public static Dim Function (Func function) { return new DimFunc (function); } - /// - /// Creates an Absolute from the specified integer value. - /// - /// The Absolute . - /// The value to convert to the pos. - public static implicit operator Dim (int n) => new DimAbsolute (n); + /// Serves as the default hash function. + /// A hash code for the current object. + public override int GetHashCode () { return Anchor (0).GetHashCode (); } - /// - /// Creates an Absolute from the specified integer value. - /// - /// The Absolute . - /// The value to convert to the . - public static Dim Sized (int n) => new DimAbsolute (n); + /// Creates a object that tracks the Height of the specified . + /// The height of the other . + /// The view that will be tracked. + public static Dim Height (View view) { return new DimView (view, 0); } - /// - /// Adds a to a , yielding a new . - /// - /// The first to add. - /// The second to add. - /// The that is the sum of the values of left and right. - public static Dim operator + (Dim left, Dim right) - { - if (left is DimAbsolute && right is DimAbsolute) { - return new DimAbsolute (left.Anchor (0) + right.Anchor (0)); - } - var newDim = new DimCombine (true, left, right); - SetDimCombine (left, newDim); - return newDim; - } + /// Adds a to a , yielding a new . + /// The first to add. + /// The second to add. + /// The that is the sum of the values of left and right. + public static Dim operator + (Dim left, Dim right) + { + if (left is DimAbsolute && right is DimAbsolute) + { + return new DimAbsolute (left.Anchor (0) + right.Anchor (0)); + } - /// - /// Subtracts a from a , yielding a new . - /// - /// The to subtract from (the minuend). - /// The to subtract (the subtrahend). - /// The that is the left minus right. - public static Dim operator - (Dim left, Dim right) - { - if (left is DimAbsolute && right is DimAbsolute) { - return new DimAbsolute (left.Anchor (0) - right.Anchor (0)); - } - var newDim = new DimCombine (false, left, right); - SetDimCombine (left, newDim); - return newDim; - } + var newDim = new DimCombine (true, left, right); + SetDimCombine (left, newDim); - // BUGBUG: newPos is never used. - static void SetDimCombine (Dim left, DimCombine newPos) => (left as DimView)?.Target.SetNeedsLayout (); + return newDim; + } - /// - /// Creates a object that tracks the Width of the specified . - /// - /// The width of the other . - /// The view that will be tracked. - public static Dim Width (View view) => new DimView (view, 1); + /// Creates an Absolute from the specified integer value. + /// The Absolute . + /// The value to convert to the pos. + public static implicit operator Dim (int n) { return new DimAbsolute (n); } - /// - /// Creates a object that tracks the Height of the specified . - /// - /// The height of the other . - /// The view that will be tracked. - public static Dim Height (View view) => new DimView (view, 0); + /// + /// Subtracts a from a , yielding a new + /// . + /// + /// The to subtract from (the minuend). + /// The to subtract (the subtrahend). + /// The that is the left minus right. + public static Dim operator - (Dim left, Dim right) + { + if (left is DimAbsolute && right is DimAbsolute) + { + return new DimAbsolute (left.Anchor (0) - right.Anchor (0)); + } - /// Serves as the default hash function. - /// A hash code for the current object. - public override int GetHashCode () => Anchor (0).GetHashCode (); + var newDim = new DimCombine (false, left, right); + SetDimCombine (left, newDim); - /// Determines whether the specified object is equal to the current object. - /// The object to compare with the current object. - /// - /// if the specified object is equal to the current object; otherwise, . - /// - public override bool Equals (object other) => other is Dim abs && abs == this; + return newDim; + } - // Helper class to provide dynamic value by the execution of a function that returns an integer. - internal class DimFunc : Dim { - readonly Func _function; + /// Creates a percentage object that is a percentage of the width or height of the SuperView. + /// The percent object. + /// A value between 0 and 100 representing the percentage. + /// + /// If true the Percent is computed based on the remaining space after the X/Y anchor positions. If false + /// is computed based on the whole original space. + /// + /// + /// This initializes a that is centered horizontally, is 50% of the way down, is 30% the height, + /// and is 80% the width of the it added to. + /// + /// var textView = new TextView () { + /// X = Pos.Center (), + /// Y = Pos.Percent (50), + /// Width = Dim.Percent (80), + /// Height = Dim.Percent (30), + /// }; + /// + /// + public static Dim Percent (float n, bool r = false) + { + if (n is < 0 or > 100) + { + throw new ArgumentException ("Percent value must be between 0 and 100"); + } - public DimFunc (Func n) => _function = n; + return new DimFactor (n / 100, r); + } - internal override int Anchor (int width) => _function (); + /// Creates an Absolute from the specified integer value. + /// The Absolute . + /// The value to convert to the . + public static Dim Sized (int n) { return new DimAbsolute (n); } - public override string ToString () => $"DimFunc({_function ()})"; + /// Creates a object that tracks the Width of the specified . + /// The width of the other . + /// The view that will be tracked. + public static Dim Width (View view) { return new DimView (view, 1); } - public override int GetHashCode () => _function.GetHashCode (); + internal virtual int Anchor (int width) { return 0; } - public override bool Equals (object other) => other is DimFunc f && f._function () == _function (); - } + // BUGBUG: newPos is never used. + private static void SetDimCombine (Dim left, DimCombine newPos) { (left as DimView)?.Target.SetNeedsLayout (); } - internal class DimFactor : Dim { - readonly float _factor; - readonly bool _remaining; + internal class DimAbsolute : Dim + { + private readonly int _n; + public DimAbsolute (int n) { _n = n; } - public DimFactor (float n, bool r = false) - { - _factor = n; - _remaining = r; - } + public override bool Equals (object other) { return other is DimAbsolute abs && abs._n == _n; } - internal override int Anchor (int width) => (int)(width * _factor); + public override int GetHashCode () { return _n.GetHashCode (); } - public bool IsFromRemaining () => _remaining; + public override string ToString () { return $"Absolute({_n})"; } - public override string ToString () => $"Factor({_factor},{_remaining})"; + internal override int Anchor (int width) { return _n; } + } - public override int GetHashCode () => _factor.GetHashCode (); + internal class DimAuto : Dim + { + internal readonly Dim _max; + internal readonly Dim _min; + internal readonly DimAutoStyle _style; - public override bool Equals (object other) => other is DimFactor f && f._factor == _factor && f._remaining == _remaining; - } + public DimAuto (DimAutoStyle style, Dim min, Dim max) + { + _min = min; + _max = max; + _style = style; + } + public override bool Equals (object other) { return other is DimAuto auto && auto._min == _min && auto._max == _max && auto._style == _style; } - internal class DimAbsolute : Dim { - readonly int _n; - public DimAbsolute (int n) => _n = n; + public override int GetHashCode () { return HashCode.Combine (base.GetHashCode (), _min, _max, _style); } - public override string ToString () => $"Absolute({_n})"; + public override string ToString () { return $"Auto({_style},{_min},{_max})"; } + } - internal override int Anchor (int width) => _n; + internal class DimCombine : Dim + { + internal bool _add; + internal Dim _left, _right; - public override int GetHashCode () => _n.GetHashCode (); + public DimCombine (bool add, Dim left, Dim right) + { + _left = left; + _right = right; + _add = add; + } - public override bool Equals (object other) => other is DimAbsolute abs && abs._n == _n; - } + public override string ToString () { return $"Combine({_left}{(_add ? '+' : '-')}{_right})"; } - internal class DimFill : Dim { - readonly int _margin; - public DimFill (int margin) => _margin = margin; + internal override int Anchor (int width) + { + int la = _left.Anchor (width); + int ra = _right.Anchor (width); - public override string ToString () => $"Fill({_margin})"; + if (_add) + { + return la + ra; + } - internal override int Anchor (int width) => width - _margin; + return la - ra; + } + } - public override int GetHashCode () => _margin.GetHashCode (); + internal class DimFactor : Dim + { + private readonly float _factor; + private readonly bool _remaining; - public override bool Equals (object other) => other is DimFill fill && fill._margin == _margin; - } + public DimFactor (float n, bool r = false) + { + _factor = n; + _remaining = r; + } - internal class DimAuto : Dim { - internal readonly Dim _max; - internal readonly Dim _min; - internal readonly DimAutoStyle _style; + public override bool Equals (object other) { return other is DimFactor f && f._factor == _factor && f._remaining == _remaining; } - public DimAuto (DimAutoStyle style, Dim min, Dim max) - { - _min = min; - _max = max; - _style = style; - } + public override int GetHashCode () { return _factor.GetHashCode (); } - public override string ToString () => $"Auto({_style},{_min},{_max})"; + public bool IsFromRemaining () { return _remaining; } - public override int GetHashCode () => HashCode.Combine (base.GetHashCode (), _min, _max, _style); + public override string ToString () { return $"Factor({_factor},{_remaining})"; } - public override bool Equals (object other) => other is DimAuto auto && auto._min == _min && auto._max == _max && auto._style == _style; - } + internal override int Anchor (int width) { return (int)(width * _factor); } + } - internal class DimCombine : Dim { - internal bool _add; - internal Dim _left, _right; + internal class DimFill : Dim + { + private readonly int _margin; + public DimFill (int margin) { _margin = margin; } - public DimCombine (bool add, Dim left, Dim right) - { - _left = left; - _right = right; - _add = add; - } + public override bool Equals (object other) { return other is DimFill fill && fill._margin == _margin; } - internal override int Anchor (int width) - { - var la = _left.Anchor (width); - var ra = _right.Anchor (width); - if (_add) { - return la + ra; - } - return la - ra; - } + public override int GetHashCode () { return _margin.GetHashCode (); } - public override string ToString () => $"Combine({_left}{(_add ? '+' : '-')}{_right})"; - } + public override string ToString () { return $"Fill({_margin})"; } - internal class DimView : Dim { - readonly int _side; + internal override int Anchor (int width) { return width - _margin; } + } - public DimView (View view, int side) - { - Target = view; - _side = side; - } + // Helper class to provide dynamic value by the execution of a function that returns an integer. + internal class DimFunc : Dim + { + private readonly Func _function; - public View Target { get; init; } + public DimFunc (Func n) { _function = n; } - internal override int Anchor (int width) => _side switch { - 0 => Target.Frame.Height, - 1 => Target.Frame.Width, - _ => 0 - }; + public override bool Equals (object other) { return other is DimFunc f && f._function () == _function (); } - public override string ToString () - { - if (Target == null) { - throw new NullReferenceException (); - } - var tside = _side switch { - 0 => "Height", - 1 => "Width", - _ => "unknown" - }; - return $"View({tside},{Target})"; - } + public override int GetHashCode () { return _function.GetHashCode (); } - public override int GetHashCode () => Target.GetHashCode (); + public override string ToString () { return $"DimFunc({_function ()})"; } - public override bool Equals (object other) => other is DimView abs && abs.Target == Target; - } -} \ No newline at end of file + internal override int Anchor (int width) { return _function (); } + } + + internal class DimView : Dim + { + private readonly int _side; + + public DimView (View view, int side) + { + Target = view; + _side = side; + } + + public View Target { get; init; } + + public override bool Equals (object other) { return other is DimView abs && abs.Target == Target; } + + public override int GetHashCode () { return Target.GetHashCode (); } + + public override string ToString () + { + if (Target == null) + { + throw new NullReferenceException (); + } + + string tside = _side switch + { + 0 => "Height", + 1 => "Width", + _ => "unknown" + }; + + return $"View({tside},{Target})"; + } + + internal override int Anchor (int width) + { + return _side switch + { + 0 => Target.Frame.Height, + 1 => Target.Frame.Width, + _ => 0 + }; + } + } +} diff --git a/Terminal.Gui/View/Layout/SizeChangedEventArgs.cs b/Terminal.Gui/View/Layout/SizeChangedEventArgs.cs index 2e0ae4502..1c87d3a07 100644 --- a/Terminal.Gui/View/Layout/SizeChangedEventArgs.cs +++ b/Terminal.Gui/View/Layout/SizeChangedEventArgs.cs @@ -1,24 +1,15 @@ -using System; +namespace Terminal.Gui; -namespace Terminal.Gui; +/// Args for events about Size (e.g. Resized) +public class SizeChangedEventArgs : EventArgs +{ + /// Creates a new instance of the class. + /// + public SizeChangedEventArgs (Size size) { Size = size; } -/// -/// Args for events about Size (e.g. Resized) -/// -public class SizeChangedEventArgs : EventArgs { - /// - /// Creates a new instance of the class. - /// - /// - public SizeChangedEventArgs (Size size) => Size = size; + /// Set to to cause the resize to be cancelled, if appropriate. + public bool Cancel { get; set; } - /// - /// Gets the size the event describes. This should reflect the new/current size after the event. - /// - public Size Size { get; } - - /// - /// Set to to cause the resize to be cancelled, if appropriate. - /// - public bool Cancel { get; set; } -} \ No newline at end of file + /// Gets the size the event describes. This should reflect the new/current size after the event. + public Size Size { get; } +} diff --git a/Terminal.Gui/View/Layout/ViewLayout.cs b/Terminal.Gui/View/Layout/ViewLayout.cs index 8f55a6e1f..abd81fb83 100644 --- a/Terminal.Gui/View/Layout/ViewLayout.cs +++ b/Terminal.Gui/View/Layout/ViewLayout.cs @@ -1,1386 +1,1513 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; +using System.ComponentModel; using System.Diagnostics; -using System.Linq; namespace Terminal.Gui; /// -/// -/// Indicates the LayoutStyle for the . -/// -/// +/// Indicates the LayoutStyle for the . +/// /// If Absolute, the , , , and -/// -/// objects are all absolute values and are not relative. The position and size of the view is described by -/// . -/// -/// +/// objects are all absolute values and are not relative. The position and size of the +/// view is described by . +/// +/// /// If Computed, one or more of the , , , or -/// -/// objects are relative to the and are computed at layout time. -/// -/// -/// Indicates the LayoutStyle for the . -/// -/// +/// objects are relative to the and are computed at layout +/// time. +/// +/// Indicates the LayoutStyle for the . +/// /// If Absolute, the , , , and -/// -/// objects are all absolute values and are not relative. The position and size of the view is described by -/// . -/// -/// +/// objects are all absolute values and are not relative. The position and size of the +/// view is described by . +/// +/// /// If Computed, one or more of the , , , or -/// -/// objects are relative to the and are computed at layout time. -/// +/// objects are relative to the and are computed at layout +/// time. +/// /// -public enum LayoutStyle { - /// - /// Indicates the , , , and - /// objects are all absolute values and are not relative. The position and size of the view is described by - /// . - /// - Absolute, +public enum LayoutStyle +{ + /// + /// Indicates the , , , and + /// objects are all absolute values and are not relative. The position and size of the view is described by + /// . + /// + Absolute, - /// - /// Indicates one or more of the , , , or - /// - /// objects are relative to the and are computed at layout time. The position and size of the - /// view - /// will be computed based on these objects at layout time. will provide the absolute computed - /// values. - /// - Computed + /// + /// Indicates one or more of the , , , or + /// objects are relative to the and are computed at layout time. + /// The position and size of the view will be computed based on these objects at layout time. + /// will provide the absolute computed values. + /// + Computed } -public partial class View { - bool _autoSize; - Rect _frame; - Dim _height = Dim.Sized (0); - Dim _width = Dim.Sized (0); - Pos _x = Pos.At (0); - Pos _y = Pos.At (0); +public partial class View +{ + private bool _autoSize; + private Rect _frame; + private Dim _height = Dim.Sized (0); + private Dim _width = Dim.Sized (0); + private Pos _x = Pos.At (0); + private Pos _y = Pos.At (0); - /// - /// Gets or sets the absolute location and dimension of the view. - /// - /// - /// The rectangle describing absolute location and dimension of the view, - /// in coordinates relative to the 's . - /// - /// - /// - /// Frame is relative to the 's . - /// - /// - /// Setting Frame will set , , , and - /// to the values of the corresponding properties of the parameter. - /// - /// - /// This causes to be . - /// - /// - /// Altering the Frame will eventually (when the view hierarchy is next laid out via see cref="LayoutSubviews"/>) - /// cause and methods to be called. - /// - /// - public Rect Frame { - get => _frame; - set { - _frame = new Rect (value.X, value.Y, Math.Max (value.Width, 0), Math.Max (value.Height, 0)); + /// + /// Gets or sets a flag that determines whether the View will be automatically resized to fit the + /// within . + /// + /// The default is . Set to to turn on AutoSize. If + /// then and will be used if can + /// fit; if won't fit the view will be resized as needed. + /// + /// + /// If is set to then and + /// will be changed to if they are not already. + /// + /// + /// If is set to then and + /// will left unchanged. + /// + /// + public virtual bool AutoSize + { + get => _autoSize; + set + { + bool v = ResizeView (value); + TextFormatter.AutoSize = v; - // If Frame gets set, by definition, the View is now LayoutStyle.Absolute, so - // set all Pos/Dim to Absolute values. - _x = _frame.X; - _y = _frame.Y; - _width = _frame.Width; - _height = _frame.Height; + if (_autoSize != v) + { + _autoSize = v; + TextFormatter.NeedsFormat = true; + UpdateTextFormatterText (); + OnResizeNeeded (); + } + } + } - // TODO: Figure out if the below can be optimized. - if (IsInitialized /*|| LayoutStyle == LayoutStyle.Absolute*/) { - LayoutAdornments (); - SetTextFormatterSize (); - SetNeedsLayout (); - SetNeedsDisplay (); - } - } - } + /// + /// The adornment (specified as a ) inside of the view that offsets the + /// from the . The Border provides the space for a visual border (drawn using line-drawing glyphs) + /// and the Title. The Border expands inward; in other words if `Border.Thickness.Top == 2` the border and title will + /// take up the first row and the second row will be filled with spaces. + /// + /// + /// provides a simple helper for turning a simple border frame on or off. + /// + /// The adornments (, , and ) are not part of the + /// View's content and are not clipped by the View's Clip Area. + /// + /// + /// Changing the size of a frame (, , or ) will change + /// the size of the and trigger to update the layout of the + /// and its . + /// + /// + public Border Border { get; private set; } - /// - /// The frame (specified as a ) that separates a View from other SubViews of the same SuperView. - /// The margin offsets the from the . - /// - /// - /// - /// The adornments (, , and ) are not part of the View's - /// content and are not clipped by the View's Clip Area. - /// - /// - /// Changing the size of an adornment (, , or ) - /// will change the size of and trigger to update the layout - /// of the and its . - /// - /// - public Margin Margin { get; private set; } + /// Gets or sets whether the view has a one row/col thick border. + /// + /// + /// This is a helper for manipulating the view's . Setting this property to any value other + /// than is equivalent to setting 's + /// to `1` and to the value. + /// + /// + /// Setting this property to is equivalent to setting 's + /// to `0` and to . + /// + /// For more advanced customization of the view's border, manipulate see directly. + /// + public LineStyle BorderStyle + { + get => Border.LineStyle; + set + { + if (value != LineStyle.None) + { + Border.Thickness = new Thickness (1); + } + else + { + Border.Thickness = new Thickness (0); + } - /// - /// The adornment (specified as a ) inside of the view that offsets the from the - /// . - /// The Border provides the space for a visual border (drawn using line-drawing glyphs) and the Title. - /// The Border expands inward; in other words if `Border.Thickness.Top == 2` the border and - /// title will take up the first row and the second row will be filled with spaces. - /// - /// - /// - /// provides a simple helper for turning a simple border frame on or off. - /// - /// - /// The adornments (, , and ) are not part of the View's - /// content and are not clipped by the View's Clip Area. - /// - /// - /// Changing the size of a frame (, , or ) - /// will change the size of the and trigger to update the layout - /// of the - /// and its . - /// - /// - public Border Border { get; private set; } + Border.LineStyle = value; + LayoutAdornments (); + SetNeedsLayout (); + } + } - /// - /// Gets or sets whether the view has a one row/col thick border. - /// - /// - /// - /// This is a helper for manipulating the view's . Setting this property to any value other - /// than - /// is equivalent to setting 's - /// to `1` and to the value. - /// - /// - /// Setting this property to is equivalent to setting 's - /// - /// to `0` and to . - /// - /// - /// For more advanced customization of the view's border, manipulate see directly. - /// - /// - public LineStyle BorderStyle { - get => Border.LineStyle; - set { - if (value != LineStyle.None) { - Border.Thickness = new Thickness (1); - } else { - Border.Thickness = new Thickness (0); - } - Border.LineStyle = value; - LayoutAdornments (); - SetNeedsLayout (); - } - } - - /// - /// The frame (specified as a ) inside of the view that offsets the from the - /// . - /// - /// - /// - /// The adornments (, , and ) are not part of the View's - /// content and are not clipped by the View's Clip Area. - /// - /// - /// Changing the size of a frame (, , or ) - /// will change the size of the and trigger to update the layout - /// of the - /// and its . - /// - /// - public Padding Padding { get; private set; } - - /// - /// - /// Gets the thickness describing the sum of the Adornments' thicknesses. - /// - /// - /// A thickness that describes the sum of the Adornments' thicknesses. - public Thickness GetAdornmentsThickness () - { - int left = Margin.Thickness.Left + Border.Thickness.Left + Padding.Thickness.Left; - int top = Margin.Thickness.Top + Border.Thickness.Top + Padding.Thickness.Top; - int right = Margin.Thickness.Right + Border.Thickness.Right + Padding.Thickness.Right; - int bottom = Margin.Thickness.Bottom + Border.Thickness.Bottom + Padding.Thickness.Bottom; - return new Thickness (left, top, right, bottom); - } - - /// - /// Helper to get the X and Y offset of the Bounds from the Frame. This is the sum of the Left and Top properties of - /// , and . - /// - public Point GetBoundsOffset () => new (Padding?.Thickness.GetInside (Padding.Frame).X ?? 0, Padding?.Thickness.GetInside (Padding.Frame).Y ?? 0); - - /// - /// This internal method is overridden by Adornment to do nothing to prevent recursion during View construction. - /// And, because Adornments don't have Adornments. It's internal to support unit tests. - /// - /// - /// - /// - internal virtual Adornment CreateAdornment (Type adornmentType) - { - void ThicknessChangedHandler (object sender, EventArgs e) - { - if (IsInitialized) { - LayoutAdornments (); - } - SetNeedsLayout (); - SetNeedsDisplay (); - } - - Adornment adornment; - - adornment = Activator.CreateInstance (adornmentType, this) as Adornment; - adornment.ThicknessChanged += ThicknessChangedHandler; - - return adornment; - } - - /// - /// Controls how the View's is computed during . If the style is set to - /// , LayoutSubviews does not change the . - /// If the style is the is updated using - /// the , , , and properties. - /// - /// - /// - /// Setting this property to will cause to determine the - /// size and position of the view. and will be set to using . - /// - /// - /// Setting this property to will cause the view to use the method to - /// size and position of the view. If either of the and properties are `null` they will be set to using - /// the current value of . - /// If either of the and properties are `null` they will be set to using . - /// - /// - /// The layout style. - public LayoutStyle LayoutStyle { - get { - if (_x is Pos.PosAbsolute && _y is Pos.PosAbsolute && _width is Dim.DimAbsolute && _height is Dim.DimAbsolute) { - return LayoutStyle.Absolute; - } - return LayoutStyle.Computed; - } - } - - /// - /// The bounds represent the View-relative rectangle used for this view; the area inside of the view where subviews and - /// content are presented. - /// - /// The rectangle describing the location and size of the area where the views' subviews and content are drawn. - /// - /// - /// If is the value of Bounds is indeterminate until - /// the view has been initialized ( is true) and has been - /// called. - /// - /// - /// Updates to the Bounds updates , and has the same effect as updating the - /// . - /// - /// - /// Altering the Bounds will eventually (when the view is next laid out) cause the - /// - /// and methods to be called. - /// - /// - /// Because coordinates are relative to the upper-left corner of the , - /// the coordinates of the upper-left corner of the rectangle returned by this property are (0,0). - /// Use this property to obtain the size of the area of the view for tasks such as drawing the view's contents. - /// - /// - public virtual Rect Bounds { - get { + /// + /// The bounds represent the View-relative rectangle used for this view; the area inside of the view where subviews and + /// content are presented. + /// + /// The rectangle describing the location and size of the area where the views' subviews and content are drawn. + /// + /// + /// If is the value of Bounds is indeterminate until + /// the view has been initialized ( is true) and has been + /// called. + /// + /// + /// Updates to the Bounds updates , and has the same effect as updating the + /// . + /// + /// + /// Altering the Bounds will eventually (when the view is next laid out) cause the + /// and methods to be called. + /// + /// + /// Because coordinates are relative to the upper-left corner of the , the + /// coordinates of the upper-left corner of the rectangle returned by this property are (0,0). Use this property to + /// obtain the size of the area of the view for tasks such as drawing the view's contents. + /// + /// + public virtual Rect Bounds + { + get + { #if DEBUG - if (LayoutStyle == LayoutStyle.Computed && !IsInitialized) { - Debug.WriteLine ($"WARNING: Bounds is being accessed before the View has been initialized. This is likely a bug in {this}"); - } + if (LayoutStyle == LayoutStyle.Computed && !IsInitialized) + { + Debug.WriteLine ($"WARNING: Bounds is being accessed before the View has been initialized. This is likely a bug in {this}"); + } #endif // DEBUG - // BUGBUG: I think there's a bug here. This should be && not || - if (Margin == null || Border == null || Padding == null) { - return new Rect (default, Frame.Size); - } - var width = Math.Max (0, Frame.Size.Width - Margin.Thickness.Horizontal - Border.Thickness.Horizontal - Padding.Thickness.Horizontal); - var height = Math.Max (0, Frame.Size.Height - Margin.Thickness.Vertical - Border.Thickness.Vertical - Padding.Thickness.Vertical); - return new Rect (Point.Empty, new Size (width, height)); - } - set { - // TODO: Should we enforce Bounds.X/Y == 0? The code currently ignores value.X/Y which is - // TODO: correct behavior, but is silent. Perhaps an exception? + + // BUGBUG: I think there's a bug here. This should be && not || + if (Margin == null || Border == null || Padding == null) + { + return new Rect (default (Point), Frame.Size); + } + + int width = Math.Max (0, Frame.Size.Width - Margin.Thickness.Horizontal - Border.Thickness.Horizontal - Padding.Thickness.Horizontal); + int height = Math.Max (0, Frame.Size.Height - Margin.Thickness.Vertical - Border.Thickness.Vertical - Padding.Thickness.Vertical); + + return new Rect (Point.Empty, new Size (width, height)); + } + set + { + // TODO: Should we enforce Bounds.X/Y == 0? The code currently ignores value.X/Y which is + // TODO: correct behavior, but is silent. Perhaps an exception? #if DEBUG - if (value.Location != Point.Empty) { - Debug.WriteLine ($"WARNING: Bounds.Location must always be 0,0. Location ({value.Location}) is ignored. {this}"); - } + if (value.Location != Point.Empty) + { + Debug.WriteLine ($"WARNING: Bounds.Location must always be 0,0. Location ({value.Location}) is ignored. {this}"); + } #endif // DEBUG - Frame = new Rect (Frame.Location, - new Size ( - value.Size.Width + Margin.Thickness.Horizontal + Border.Thickness.Horizontal + Padding.Thickness.Horizontal, - value.Size.Height + Margin.Thickness.Vertical + Border.Thickness.Vertical + Padding.Thickness.Vertical - ) - ); - } - } + Frame = new Rect ( + Frame.Location, + new Size ( + value.Size.Width + Margin.Thickness.Horizontal + Border.Thickness.Horizontal + Padding.Thickness.Horizontal, + value.Size.Height + Margin.Thickness.Vertical + Border.Thickness.Vertical + Padding.Thickness.Vertical + ) + ); + } + } - /// - /// Gets or sets the X position for the view (the column). - /// - /// The object representing the X position. - /// - /// - /// If set to a relative value (e.g. ) the value is indeterminate until the - /// view has been initialized ( is true) and has been - /// called. - /// - /// - /// Changing this property will eventually (when the view is next drawn) cause the - /// and - /// methods to be called. - /// - /// - /// Changing this property will cause to be updated. If - /// the new value is not of type the will change to - /// . - /// - /// - /// The default value is Pos.At (0). - /// - /// - public Pos X { - get => VerifyIsInitialized (_x, nameof (X)); - set { - _x = value ?? throw new ArgumentNullException (nameof (value), @$"{nameof (X)} cannot be null"); - OnResizeNeeded (); - } - } + /// Gets or sets the absolute location and dimension of the view. + /// + /// The rectangle describing absolute location and dimension of the view, in coordinates relative to the + /// 's . + /// + /// + /// Frame is relative to the 's . + /// + /// Setting Frame will set , , , and to the + /// values of the corresponding properties of the parameter. + /// + /// This causes to be . + /// + /// Altering the Frame will eventually (when the view hierarchy is next laid out via see cref="LayoutSubviews"/>) + /// cause and methods to be called. + /// + /// + public Rect Frame + { + get => _frame; + set + { + _frame = new Rect (value.X, value.Y, Math.Max (value.Width, 0), Math.Max (value.Height, 0)); - /// - /// Gets or sets the Y position for the view (the row). - /// - /// The object representing the Y position. - /// - /// - /// If set to a relative value (e.g. ) the value is indeterminate until the - /// view has been initialized ( is true) and has been - /// called. - /// - /// - /// Changing this property will eventually (when the view is next drawn) cause the - /// and - /// methods to be called. - /// - /// - /// Changing this property will cause to be updated. If - /// the new value is not of type the will change to - /// . - /// - /// - /// The default value is Pos.At (0). - /// - /// - public Pos Y { - get => VerifyIsInitialized (_y, nameof (Y)); - set { - _y = value ?? throw new ArgumentNullException (nameof (value), @$"{nameof (Y)} cannot be null"); - OnResizeNeeded (); - } - } + // If Frame gets set, by definition, the View is now LayoutStyle.Absolute, so + // set all Pos/Dim to Absolute values. + _x = _frame.X; + _y = _frame.Y; + _width = _frame.Width; + _height = _frame.Height; - /// - /// Gets or sets the width dimension of the view. - /// - /// The object representing the width of the view (the number of columns). - /// - /// - /// If set to a relative value (e.g. ) the value is indeterminate until the - /// view has been initialized ( is true) and has been - /// called. - /// - /// - /// Changing this property will eventually (when the view is next drawn) cause the - /// - /// and methods to be called. - /// - /// - /// Changing this property will cause to be updated. If - /// the new value is not of type the will change to - /// . - /// - /// - /// The default value is Dim.Sized (0). - /// - /// - public Dim Width { - get => VerifyIsInitialized (_width, nameof (Width)); - set { - _width = value ?? throw new ArgumentNullException (nameof (value), @$"{nameof (Width)} cannot be null"); + // TODO: Figure out if the below can be optimized. + if (IsInitialized /*|| LayoutStyle == LayoutStyle.Absolute*/) + { + LayoutAdornments (); + SetTextFormatterSize (); + SetNeedsLayout (); + SetNeedsDisplay (); + } + } + } - if (ValidatePosDim) { - CheckDimAuto (); - var isValidNewAutSize = AutoSize && IsValidAutoSizeWidth (_width); + /// + /// Gets or sets the height dimension of the view. Gets or sets whether validation of and + /// occurs. + /// + /// The object representing the height of the view (the number of rows). + /// + /// + /// If set to a relative value (e.g. ) the value is indeterminate until the view has + /// been initialized ( is true) and has been + /// called. + /// + /// + /// Changing this property will eventually (when the view is next drawn) cause the + /// and methods to be called. + /// + /// + /// Changing this property will cause to be updated. If the new value is not of type + /// the will change to . + /// + /// The default value is Dim.Sized (0). + /// + public Dim Height + { + get => VerifyIsInitialized (_height, nameof (Height)); + set + { + _height = value ?? throw new ArgumentNullException (nameof (value), @$"{nameof (Height)} cannot be null"); - if (IsAdded && AutoSize && !isValidNewAutSize) { - throw new InvalidOperationException ("Must set AutoSize to false before set the Width."); - } - } - OnResizeNeeded (); - } - } + if (ValidatePosDim) + { + CheckDimAuto (); + bool isValidNewAutSize = AutoSize && IsValidAutoSizeHeight (_height); - /// - /// Gets or sets the height dimension of the view. - /// Gets or sets whether validation of and occurs. - /// - /// The object representing the height of the view (the number of rows). - /// - /// - /// If set to a relative value (e.g. ) the value is indeterminate until the - /// view has been initialized ( is true) and has been - /// called. - /// - /// - /// Changing this property will eventually (when the view is next drawn) cause the - /// - /// and methods to be called. - /// - /// - /// Changing this property will cause to be updated. If - /// the new value is not of type the will change to - /// . - /// - /// - /// The default value is Dim.Sized (0). - /// - /// - public Dim Height { - get => VerifyIsInitialized (_height, nameof (Height)); - set { - _height = value ?? throw new ArgumentNullException (nameof (value), @$"{nameof (Height)} cannot be null"); + if (IsAdded && AutoSize && !isValidNewAutSize) + { + throw new InvalidOperationException ("Must set AutoSize to false before setting the Height."); + } + } - if (ValidatePosDim) { - CheckDimAuto (); - var isValidNewAutSize = AutoSize && IsValidAutoSizeHeight (_height); + OnResizeNeeded (); + } + } - if (IsAdded && AutoSize && !isValidNewAutSize) { - throw new InvalidOperationException ("Must set AutoSize to false before setting the Height."); - } - } - OnResizeNeeded (); - } - } + /// + /// Controls how the View's is computed during . If the style is set to + /// , LayoutSubviews does not change the . If the style is + /// the is updated using the , , + /// , and properties. + /// + /// + /// + /// Setting this property to will cause to determine the + /// size and position of the view. and will be set to + /// using . + /// + /// + /// Setting this property to will cause the view to use the + /// method to size and position of the view. If either of the and + /// properties are `null` they will be set to using the current value + /// of . If either of the and properties are `null` + /// they will be set to using . + /// + /// + /// The layout style. + public LayoutStyle LayoutStyle + { + get + { + if (_x is Pos.PosAbsolute && _y is Pos.PosAbsolute && _width is Dim.DimAbsolute && _height is Dim.DimAbsolute) + { + return LayoutStyle.Absolute; + } - /// - /// Gets or sets whether validation of and occurs. - /// - /// - /// Setting this to will enable validation of , , , - /// and - /// during set operations and in . If invalid settings are discovered exceptions will be thrown - /// indicating the error. - /// This will impose a performance penalty and thus should only be used for debugging. - /// - public bool ValidatePosDim { get; set; } + return LayoutStyle.Computed; + } + } - /// - /// Throws an if any SubViews are using Dim objects that depend on this - /// Views dimensions. - /// - /// - void CheckDimAuto () - { - if (!ValidatePosDim || !IsInitialized || (Width is not Dim.DimAuto && Height is not Dim.DimAuto)) { - return; - } + /// + /// The frame (specified as a ) that separates a View from other SubViews of the same SuperView. + /// The margin offsets the from the . + /// + /// + /// + /// The adornments (, , and ) are not part of the + /// View's content and are not clipped by the View's Clip Area. + /// + /// + /// Changing the size of an adornment (, , or ) will + /// change the size of and trigger to update the layout of the + /// and its . + /// + /// + public Margin Margin { get; private set; } - void ThrowInvalid (View view, object checkPosDim, string name) - { - // TODO: Figure out how to make CheckDimAuto deal with PosCombine - object bad = null; - switch (checkPosDim) { - case Pos pos and not Pos.PosAbsolute and not Pos.PosView and not Pos.PosCombine: - bad = pos; - break; - case Pos pos and Pos.PosCombine: - // Recursively check for not Absolute or not View - ThrowInvalid (view, (pos as Pos.PosCombine)._left, name); - ThrowInvalid (view, (pos as Pos.PosCombine)._right, name); - break; + /// + /// The frame (specified as a ) inside of the view that offsets the from + /// the . + /// + /// + /// + /// The adornments (, , and ) are not part of the + /// View's content and are not clipped by the View's Clip Area. + /// + /// + /// Changing the size of a frame (, , or ) will change + /// the size of the and trigger to update the layout of the + /// and its . + /// + /// + public Padding Padding { get; private set; } - case Dim dim and not Dim.DimAbsolute and not Dim.DimView and not Dim.DimCombine: - bad = dim; - break; - case Dim dim and Dim.DimCombine: - // Recursively check for not Absolute or not View - ThrowInvalid (view, (dim as Dim.DimCombine)._left, name); - ThrowInvalid (view, (dim as Dim.DimCombine)._right, name); - break; - } + /// Gets or sets whether validation of and occurs. + /// + /// Setting this to will enable validation of , , + /// , and during set operations and in . If invalid + /// settings are discovered exceptions will be thrown indicating the error. This will impose a performance penalty and + /// thus should only be used for debugging. + /// + public bool ValidatePosDim { get; set; } - if (bad != null) { - throw new InvalidOperationException ( - @$"{view.GetType ().Name}.{name} = {bad.GetType ().Name} which depends on the SuperView's dimensions and the SuperView uses Dim.Auto."); - } - } + /// Gets or sets the width dimension of the view. + /// The object representing the width of the view (the number of columns). + /// + /// + /// If set to a relative value (e.g. ) the value is indeterminate until the view has + /// been initialized ( is true) and has been + /// called. + /// + /// + /// Changing this property will eventually (when the view is next drawn) cause the + /// and methods to be called. + /// + /// + /// Changing this property will cause to be updated. If the new value is not of type + /// the will change to . + /// + /// The default value is Dim.Sized (0). + /// + public Dim Width + { + get => VerifyIsInitialized (_width, nameof (Width)); + set + { + _width = value ?? throw new ArgumentNullException (nameof (value), @$"{nameof (Width)} cannot be null"); - // Verify none of the subviews are using Dim objects that depend on the SuperView's dimensions. - foreach (var view in Subviews) { - if (Width is Dim.DimAuto { _min: null }) { - ThrowInvalid (view, view.Width, nameof (view.Width)); - ThrowInvalid (view, view.X, nameof (view.X)); - } - if (Height is Dim.DimAuto { _min: null }) { - ThrowInvalid (view, view.Height, nameof (view.Height)); - ThrowInvalid (view, view.Y, nameof (view.Y)); - } - } - } + if (ValidatePosDim) + { + CheckDimAuto (); + bool isValidNewAutSize = AutoSize && IsValidAutoSizeWidth (_width); - internal bool LayoutNeeded { get; private set; } = true; + if (IsAdded && AutoSize && !isValidNewAutSize) + { + throw new InvalidOperationException ("Must set AutoSize to false before set the Width."); + } + } - /// - /// Gets or sets a flag that determines whether the View will be automatically resized to fit the - /// within . - /// - /// The default is . Set to to turn on AutoSize. If - /// then - /// and will be used if can fit; - /// if won't fit the view will be resized as needed. - /// - /// - /// If is set to then and - /// will be changed to if they are not already. - /// - /// - /// If is set to then and - /// will left unchanged. - /// - /// - public virtual bool AutoSize { - get => _autoSize; - set { - var v = ResizeView (value); - TextFormatter.AutoSize = v; - if (_autoSize != v) { - _autoSize = v; - TextFormatter.NeedsFormat = true; - UpdateTextFormatterText (); - OnResizeNeeded (); - } - } - } + OnResizeNeeded (); + } + } - /// - /// Event called only once when the is being initialized for the first time. - /// Allows configurations and assignments to be performed before the being shown. - /// This derived from to allow notify all the views that are being - /// initialized. - /// - public event EventHandler Initialized; + /// Gets or sets the X position for the view (the column). + /// The object representing the X position. + /// + /// + /// If set to a relative value (e.g. ) the value is indeterminate until the view has been + /// initialized ( is true) and has been called. + /// + /// + /// Changing this property will eventually (when the view is next drawn) cause the + /// and methods to be called. + /// + /// + /// Changing this property will cause to be updated. If the new value is not of type + /// the will change to . + /// + /// The default value is Pos.At (0). + /// + public Pos X + { + get => VerifyIsInitialized (_x, nameof (X)); + set + { + _x = value ?? throw new ArgumentNullException (nameof (value), @$"{nameof (X)} cannot be null"); + OnResizeNeeded (); + } + } + /// Gets or sets the Y position for the view (the row). + /// The object representing the Y position. + /// + /// + /// If set to a relative value (e.g. ) the value is indeterminate until the view has been + /// initialized ( is true) and has been called. + /// + /// + /// Changing this property will eventually (when the view is next drawn) cause the + /// and methods to be called. + /// + /// + /// Changing this property will cause to be updated. If the new value is not of type + /// the will change to . + /// + /// The default value is Pos.At (0). + /// + public Pos Y + { + get => VerifyIsInitialized (_y, nameof (Y)); + set + { + _y = value ?? throw new ArgumentNullException (nameof (value), @$"{nameof (Y)} cannot be null"); + OnResizeNeeded (); + } + } + internal bool LayoutNeeded { get; private set; } = true; - // Diagnostics to highlight when X or Y is read before the view has been initialized - Pos VerifyIsInitialized (Pos pos, string member) - { + /// + /// Event called only once when the is being initialized for the first time. Allows configurations + /// and assignments to be performed before the being shown. This derived from + /// to allow notify all the views that are being initialized. + /// + public event EventHandler Initialized; + + /// + /// Converts a -relative coordinate to a screen-relative coordinate. The output is optionally + /// clamped to the screen dimensions. + /// + /// -relative column. + /// -relative row. + /// Absolute column; screen-relative. + /// Absolute row; screen-relative. + /// + /// If , and will be clamped to the screen dimensions + /// (will never be negative and will always be less than and + /// , respectively. + /// + public virtual void BoundsToScreen (int x, int y, out int rx, out int ry, bool clamped = true) + { + Point boundsOffset = GetBoundsOffset (); + rx = x + Frame.X + boundsOffset.X; + ry = y + Frame.Y + boundsOffset.Y; + + View super = SuperView; + + while (super != null) + { + boundsOffset = super.GetBoundsOffset (); + rx += super.Frame.X + boundsOffset.X; + ry += super.Frame.Y + boundsOffset.Y; + super = super.SuperView; + } + + // The following ensures that the cursor is always in the screen boundaries. + if (clamped) + { + ry = Math.Min (ry, Driver.Rows - 1); + rx = Math.Min (rx, Driver.Cols - 1); + } + } + + /// Converts a -relative region to a screen-relative region. + public Rect BoundsToScreen (Rect region) + { + BoundsToScreen (region.X, region.Y, out int x, out int y, false); + + return new Rect (x, y, region.Width, region.Height); + } + + /// Finds which view that belong to the superview at the provided location. + /// The superview where to look for. + /// The column location in the superview. + /// The row location in the superview. + /// The found view screen relative column location. + /// The found view screen relative row location. + /// + /// The view that was found at the and coordinates. + /// if no view was found. + /// + public static View FindDeepestView (View start, int x, int y, out int resx, out int resy) + { + resy = resx = 0; + + if (start == null || !start.Frame.Contains (x, y)) + { + return null; + } + + Rect startFrame = start.Frame; + + if (start.InternalSubviews != null) + { + int count = start.InternalSubviews.Count; + + if (count > 0) + { + Point boundsOffset = start.GetBoundsOffset (); + int rx = x - (startFrame.X + boundsOffset.X); + int ry = y - (startFrame.Y + boundsOffset.Y); + + for (int i = count - 1; i >= 0; i--) + { + View v = start.InternalSubviews [i]; + + if (v.Visible && v.Frame.Contains (rx, ry)) + { + View deep = FindDeepestView (v, rx, ry, out resx, out resy); + + if (deep == null) + { + return v; + } + + return deep; + } + } + } + } + + resx = x - startFrame.X; + resy = y - startFrame.Y; + + return start; + } + + /// Gets the with a screen-relative location. + /// The location and size of the view in screen-relative coordinates. + public virtual Rect FrameToScreen () + { + Rect ret = Frame; + View super = SuperView; + + while (super != null) + { + Point boundsOffset = super.GetBoundsOffset (); + ret.X += super.Frame.X + boundsOffset.X; + ret.Y += super.Frame.Y + boundsOffset.Y; + super = super.SuperView; + } + + return ret; + } + + /// + /// Gets the thickness describing the sum of the Adornments' thicknesses. + /// + /// A thickness that describes the sum of the Adornments' thicknesses. + public Thickness GetAdornmentsThickness () + { + int left = Margin.Thickness.Left + Border.Thickness.Left + Padding.Thickness.Left; + int top = Margin.Thickness.Top + Border.Thickness.Top + Padding.Thickness.Top; + int right = Margin.Thickness.Right + Border.Thickness.Right + Padding.Thickness.Right; + int bottom = Margin.Thickness.Bottom + Border.Thickness.Bottom + Padding.Thickness.Bottom; + + return new Thickness (left, top, right, bottom); + } + + /// + /// Helper to get the X and Y offset of the Bounds from the Frame. This is the sum of the Left and Top properties of + /// , and . + /// + public Point GetBoundsOffset () + { + return new Point (Padding?.Thickness.GetInside (Padding.Frame).X ?? 0, Padding?.Thickness.GetInside (Padding.Frame).Y ?? 0); + } + + /// Fired after the View's method has completed. + /// + /// Subscribe to this event to perform tasks when the has been resized or the layout has otherwise + /// changed. + /// + public event EventHandler LayoutComplete; + + /// Fired after the View's method has completed. + /// + /// Subscribe to this event to perform tasks when the has been resized or the layout has otherwise + /// changed. + /// + public event EventHandler LayoutStarted; + + /// + /// Invoked when a view starts executing or when the dimensions of the view have changed, for example in response to + /// the container view or terminal resizing. + /// + /// + /// + /// The position and dimensions of the view are indeterminate until the view has been initialized. Therefore, the + /// behavior of this method is indeterminate if is . + /// + /// Raises the event) before it returns. + /// + public virtual void LayoutSubviews () + { + if (!IsInitialized) + { + Debug.WriteLine ($"WARNING: LayoutSubviews called before view has been initialized. This is likely a bug in {this}"); + } + + if (!LayoutNeeded) + { + return; + } + + CheckDimAuto (); + + LayoutAdornments (); + + Rect oldBounds = Bounds; + OnLayoutStarted (new LayoutEventArgs { OldBounds = oldBounds }); + + SetTextFormatterSize (); + + // Sort out the dependencies of the X, Y, Width, Height properties + HashSet nodes = new (); + HashSet<(View, View)> edges = new (); + CollectAll (this, ref nodes, ref edges); + List ordered = TopologicalSort (SuperView, nodes, edges); + + foreach (View v in ordered) + { + if (v.Width is Dim.DimAuto || v.Height is Dim.DimAuto) + { + // If the view is auto-sized... + Rect f = v.Frame; + v._frame = new Rect (v.Frame.X, v.Frame.Y, 0, 0); + LayoutSubview (v, new Rect (GetBoundsOffset (), Bounds.Size)); + + if (v.Frame != f) + { + // The subviews changed; do it again + v.LayoutNeeded = true; + LayoutSubview (v, new Rect (GetBoundsOffset (), Bounds.Size)); + } + } + else + { + LayoutSubview (v, new Rect (GetBoundsOffset (), Bounds.Size)); + } + } + + // If the 'to' is rooted to 'from' and the layoutstyle is Computed it's a special-case. + // Use LayoutSubview with the Frame of the 'from' + if (SuperView != null && GetTopSuperView () != null && LayoutNeeded && edges.Count > 0) + { + foreach ((View from, View to) in edges) + { + LayoutSubview (to, from.Frame); + } + } + + LayoutNeeded = false; + + OnLayoutComplete (new LayoutEventArgs { OldBounds = oldBounds }); + } + + /// Converts a screen-relative coordinate to a bounds-relative coordinate. + /// The coordinate relative to this view's . + /// Screen-relative column. + /// Screen-relative row. + public Point ScreenToBounds (int x, int y) + { + Point screen = ScreenToFrame (x, y); + Point boundsOffset = GetBoundsOffset (); + + return new Point (screen.X - boundsOffset.X, screen.Y - boundsOffset.Y); + } + + /// + /// Converts a screen-relative coordinate to a Frame-relative coordinate. Frame-relative means relative to the View's + /// 's . + /// + /// The coordinate relative to the 's . + /// Screen-relative column. + /// Screen-relative row. + public Point ScreenToFrame (int x, int y) + { + Point superViewBoundsOffset = SuperView?.GetBoundsOffset () ?? Point.Empty; + var ret = new Point (x - Frame.X - superViewBoundsOffset.X, y - Frame.Y - superViewBoundsOffset.Y); + + if (SuperView != null) + { + Point superFrame = SuperView.ScreenToFrame (x - superViewBoundsOffset.X, y - superViewBoundsOffset.Y); + ret = new Point (superFrame.X - Frame.X, superFrame.Y - Frame.Y); + } + + return ret; + } + + /// Indicates that the view does not need to be laid out. + protected void ClearLayoutNeeded () { LayoutNeeded = false; } + + internal void CollectAll (View from, ref HashSet nNodes, ref HashSet<(View, View)> nEdges) + { + // BUGBUG: This should really only work on initialized subviews + foreach (View v in from.InternalSubviews /*.Where(v => v.IsInitialized)*/) + { + nNodes.Add (v); + + if (v.LayoutStyle != LayoutStyle.Computed) + { + continue; + } + + CollectPos (v.X, v, ref nNodes, ref nEdges); + CollectPos (v.Y, v, ref nNodes, ref nEdges); + CollectDim (v.Width, v, ref nNodes, ref nEdges); + CollectDim (v.Height, v, ref nNodes, ref nEdges); + } + } + + internal void CollectDim (Dim dim, View from, ref HashSet nNodes, ref HashSet<(View, View)> nEdges) + { + switch (dim) + { + case Dim.DimView dv: + // See #2461 + //if (!from.InternalSubviews.Contains (dv.Target)) { + // throw new InvalidOperationException ($"View {dv.Target} is not a subview of {from}"); + //} + if (dv.Target != this) + { + nEdges.Add ((dv.Target, from)); + } + + return; + case Dim.DimCombine dc: + CollectDim (dc._left, from, ref nNodes, ref nEdges); + CollectDim (dc._right, from, ref nNodes, ref nEdges); + + break; + } + } + + internal void CollectPos (Pos pos, View from, ref HashSet nNodes, ref HashSet<(View, View)> nEdges) + { + switch (pos) + { + case Pos.PosView pv: + // See #2461 + //if (!from.InternalSubviews.Contains (pv.Target)) { + // throw new InvalidOperationException ($"View {pv.Target} is not a subview of {from}"); + //} + if (pv.Target != this) + { + nEdges.Add ((pv.Target, from)); + } + + return; + case Pos.PosCombine pc: + CollectPos (pc._left, from, ref nNodes, ref nEdges); + CollectPos (pc._right, from, ref nNodes, ref nEdges); + + break; + } + } + + /// + /// This internal method is overridden by Adornment to do nothing to prevent recursion during View construction. And, + /// because Adornments don't have Adornments. It's internal to support unit tests. + /// + /// + /// + /// + internal virtual Adornment CreateAdornment (Type adornmentType) + { + void ThicknessChangedHandler (object sender, EventArgs e) + { + if (IsInitialized) + { + LayoutAdornments (); + } + + SetNeedsLayout (); + SetNeedsDisplay (); + } + + Adornment adornment; + + adornment = Activator.CreateInstance (adornmentType, this) as Adornment; + adornment.ThicknessChanged += ThicknessChangedHandler; + + return adornment; + } + + /// Overriden by to do nothing, as the does not have adornments. + internal virtual void LayoutAdornments () + { + if (Margin == null) + { + return; // CreateAdornments () has not been called yet + } + + if (Margin.Frame.Size != Frame.Size) + { + Margin._frame = new Rect (Point.Empty, Frame.Size); + Margin.X = 0; + Margin.Y = 0; + Margin.Width = Frame.Size.Width; + Margin.Height = Frame.Size.Height; + Margin.SetNeedsLayout (); + Margin.SetNeedsDisplay (); + } + + Rect border = Margin.Thickness.GetInside (Margin.Frame); + + if (border != Border.Frame) + { + Border._frame = new Rect (new Point (border.Location.X, border.Location.Y), border.Size); + Border.X = border.Location.X; + Border.Y = border.Location.Y; + Border.Width = border.Size.Width; + Border.Height = border.Size.Height; + Border.SetNeedsLayout (); + Border.SetNeedsDisplay (); + } + + Rect padding = Border.Thickness.GetInside (Border.Frame); + + if (padding != Padding.Frame) + { + Padding._frame = new Rect (new Point (padding.Location.X, padding.Location.Y), padding.Size); + Padding.X = padding.Location.X; + Padding.Y = padding.Location.Y; + Padding.Width = padding.Size.Width; + Padding.Height = padding.Size.Height; + Padding.SetNeedsLayout (); + Padding.SetNeedsDisplay (); + } + } + + /// + /// Raises the event. Called from before all sub-views have + /// been laid out. + /// + internal virtual void OnLayoutComplete (LayoutEventArgs args) { LayoutComplete?.Invoke (this, args); } + + /// + /// Raises the event. Called from before any subviews have + /// been laid out. + /// + internal virtual void OnLayoutStarted (LayoutEventArgs args) { LayoutStarted?.Invoke (this, args); } + + /// + /// Called whenever the view needs to be resized. This is called whenever , , + /// , , or changes. + /// + /// + /// + /// Determines the relative bounds of the and its s, and then calls + /// to update the view. + /// + /// + internal void OnResizeNeeded () + { + // TODO: Identify a real-world use-case where this API should be virtual. + // TODO: Until then leave it `internal` and non-virtual + // First try SuperView.Bounds, then Application.Top, then Driver.Bounds. + // Finally, if none of those are valid, use int.MaxValue (for Unit tests). + Rect relativeBounds = SuperView is { IsInitialized: true } ? SuperView.Bounds : + Application.Top != null && Application.Top.IsInitialized ? Application.Top.Bounds : + Application.Driver?.Bounds ?? new Rect (0, 0, int.MaxValue, int.MaxValue); + SetRelativeLayout (relativeBounds); + + // TODO: Determine what, if any of the below is actually needed here. + if (IsInitialized) + { + SetFrameToFitText (); + LayoutAdornments (); + SetTextFormatterSize (); + SetNeedsLayout (); + SetNeedsDisplay (); + } + } + + /// + /// Sets the internal flag for this View and all of it's subviews and it's SuperView. The + /// main loop will call SetRelativeLayout and LayoutSubviews for any view with set. + /// + internal void SetNeedsLayout () + { + if (LayoutNeeded) + { + return; + } + + LayoutNeeded = true; + + foreach (View view in Subviews) + { + view.SetNeedsLayout (); + } + + TextFormatter.NeedsFormat = true; + SuperView?.SetNeedsLayout (); + } + + /// + /// Applies the view's position (, ) and dimension (, and + /// ) to , given a rectangle describing the SuperView's Bounds (nominally the + /// same as this.SuperView.Bounds). + /// + /// + /// The rectangle describing the SuperView's Bounds (nominally the same as + /// this.SuperView.Bounds). + /// + internal void SetRelativeLayout (Rect superviewBounds) + { + Debug.Assert (_x != null); + Debug.Assert (_y != null); + Debug.Assert (_width != null); + Debug.Assert (_height != null); + + CheckDimAuto (); + + int newX, newW, newY, newH; + var autosize = Size.Empty; + + if (AutoSize) + { + // Note this is global to this function and used as such within the local functions defined + // below. In v2 AutoSize will be re-factored to not need to be dealt with in this function. + autosize = GetTextAutoSize (); + } + + // TODO: Since GetNewLocationAndDimension does not depend on View, it can be moved into PosDim.cs + // TODO: to make architecture more clean. Do this after DimAuto is implemented and the + // TODO: View.AutoSize stuff is removed. + + // Returns the new dimension (width or height) and location (x or y) for the View given + // the superview's Bounds + // the current Pos (View.X or View.Y) + // the current Dim (View.Width or View.Height) + // This method is called recursively if pos is Pos.PosCombine + (int newLocation, int newDimension) GetNewLocationAndDimension (bool width, Rect superviewBounds, Pos pos, Dim dim, int autosizeDimension) + { + // Gets the new dimension (width or height, dependent on `width`) of the given Dim given: + // location: the current location (x or y) + // dimension: the new dimension (width or height) (if relevant for Dim type) + // autosize: the size to use if autosize = true + // This method is recursive if d is Dim.DimCombine + int GetNewDimension (Dim d, int location, int dimension, int autosize) + { + int newDimension; + + switch (d) + { + case Dim.DimCombine combine: + // TODO: Move combine logic into DimCombine? + int leftNewDim = GetNewDimension (combine._left, location, dimension, autosize); + int rightNewDim = GetNewDimension (combine._right, location, dimension, autosize); + + if (combine._add) + { + newDimension = leftNewDim + rightNewDim; + } + else + { + newDimension = leftNewDim - rightNewDim; + } + + newDimension = AutoSize && autosize > newDimension ? autosize : newDimension; + + break; + + case Dim.DimFactor factor when !factor.IsFromRemaining (): + newDimension = d.Anchor (dimension); + newDimension = AutoSize && autosize > newDimension ? autosize : newDimension; + + break; + + case Dim.DimAuto auto: + Thickness thickness = GetAdornmentsThickness (); + var text = 0; + var subviews = 0; + + if (auto._style is Dim.DimAutoStyle.Text or Dim.DimAutoStyle.Auto) + { + if (Id == "vlabel") + { } + + text = int.Max ( + width ? TextFormatter.Size.Width : TextFormatter.Size.Height, + auto._min?.Anchor (width ? superviewBounds.Width : superviewBounds.Height) ?? 0); + } + + if (auto._style is Dim.DimAutoStyle.Subviews or Dim.DimAutoStyle.Auto) + { + subviews = Subviews.Count == 0 + ? 0 + : Subviews + .Where (v => width ? v.X is not Pos.PosAnchorEnd : v.Y is not Pos.PosAnchorEnd) + .Max (v => width ? v.Frame.X + v.Frame.Width : v.Frame.Y + v.Frame.Height); + } + + int max = int.Max (text, subviews); + + newDimension = int.Max ( + width ? max + thickness.Left + thickness.Right : max + thickness.Top + thickness.Bottom, + auto._min?.Anchor (width ? superviewBounds.Width : superviewBounds.Height) ?? 0); + + break; + + case Dim.DimAbsolute: + // DimAbsoulte.Anchor (int width) ignores width and returns n + newDimension = Math.Max (d.Anchor (0), 0); + newDimension = AutoSize && autosize > newDimension ? autosize : newDimension; + + break; + + case Dim.DimFill: + default: + newDimension = Math.Max (d.Anchor (dimension - location), 0); + newDimension = AutoSize && autosize > newDimension ? autosize : newDimension; + + break; + } + + return newDimension; + } + + int newDimension, newLocation; + int superviewDimension = width ? superviewBounds.Width : superviewBounds.Height; + + // Determine new location + switch (pos) + { + case Pos.PosCenter posCenter: + // For Center, the dimension is dependent on location, but we need to force getting the dimension first + // using a location of 0 + newDimension = Math.Max (GetNewDimension (dim, 0, superviewDimension, autosizeDimension), 0); + newLocation = posCenter.Anchor (superviewDimension - newDimension); + newDimension = Math.Max (GetNewDimension (dim, newLocation, superviewDimension, autosizeDimension), 0); + + break; + + case Pos.PosCombine combine: + // TODO: Move combine logic into PosCombine? + // TODO: Move combine logic into PosCombine? + int left, right; + (left, newDimension) = GetNewLocationAndDimension (width, superviewBounds, combine._left, dim, autosizeDimension); + (right, newDimension) = GetNewLocationAndDimension (width, superviewBounds, combine._right, dim, autosizeDimension); + + if (combine._add) + { + newLocation = left + right; + } + else + { + newLocation = left - right; + } + + newDimension = Math.Max (GetNewDimension (dim, newLocation, superviewDimension, autosizeDimension), 0); + + break; + + case Pos.PosAnchorEnd: + case Pos.PosAbsolute: + case Pos.PosFactor: + case Pos.PosFunc: + case Pos.PosView: + default: + newLocation = pos?.Anchor (superviewDimension) ?? 0; + newDimension = Math.Max (GetNewDimension (dim, newLocation, superviewDimension, autosizeDimension), 0); + + break; + } + + return (newLocation, newDimension); + } + + // horizontal/width + (newX, newW) = GetNewLocationAndDimension (true, superviewBounds, _x, _width, autosize.Width); + + // vertical/height + (newY, newH) = GetNewLocationAndDimension (false, superviewBounds, _y, _height, autosize.Height); + + var r = new Rect (newX, newY, newW, newH); + + if (Frame != r) + { + // Set the frame. Do NOT use `Frame` as it overwrites X, Y, Width, and Height, making + // the view LayoutStyle.Absolute. + _frame = r; + + if (_x is Pos.PosAbsolute) + { + _x = Frame.X; + } + + if (_y is Pos.PosAbsolute) + { + _y = Frame.Y; + } + + if (_width is Dim.DimAbsolute) + { + _width = Frame.Width; + } + + if (_height is Dim.DimAbsolute) + { + _height = Frame.Height; + } + + if (IsInitialized) + { + // TODO: Figure out what really is needed here. All unit tests (except AutoSize) pass as-is + SetTextFormatterSize (); + SetNeedsLayout (); + } + + // BUGBUG: Why is this AFTER setting Frame? Seems duplicative. + if (!SetFrameToFitText ()) + { + SetTextFormatterSize (); + } + } + } + + // https://en.wikipedia.org/wiki/Topological_sorting + internal static List TopologicalSort (View superView, IEnumerable nodes, ICollection<(View From, View To)> edges) + { + List result = new (); + + // Set of all nodes with no incoming edges + HashSet noEdgeNodes = new (nodes.Where (n => edges.All (e => !e.To.Equals (n)))); + + while (noEdgeNodes.Any ()) + { + // remove a node n from S + View n = noEdgeNodes.First (); + noEdgeNodes.Remove (n); + + // add n to tail of L + if (n != superView) + { + result.Add (n); + } + + // for each node m with an edge e from n to m do + foreach ((View From, View To) e in edges.Where (e => e.From.Equals (n)).ToArray ()) + { + View m = e.To; + + // remove edge e from the graph + edges.Remove (e); + + // if m has no other incoming edges then + if (edges.All (me => !me.To.Equals (m)) && m != superView) + { + // insert m into S + noEdgeNodes.Add (m); + } + } + } + + if (!edges.Any ()) + { + return result; + } + + foreach ((View from, View to) in edges) + { + if (from == to) + { + // if not yet added to the result, add it and remove from edge + if (result.Find (v => v == from) == null) + { + result.Add (from); + } + + edges.Remove ((from, to)); + } + else if (from.SuperView == to.SuperView) + { + // if 'from' is not yet added to the result, add it + if (result.Find (v => v == from) == null) + { + result.Add (from); + } + + // if 'to' is not yet added to the result, add it + if (result.Find (v => v == to) == null) + { + result.Add (to); + } + + // remove from edge + edges.Remove ((from, to)); + } + else if (from != superView?.GetTopSuperView (to, from) && !ReferenceEquals (from, to)) + { + if (ReferenceEquals (from.SuperView, to)) + { + throw new InvalidOperationException ($"ComputedLayout for \"{superView}\": \"{to}\" references a SubView (\"{from}\")."); + } + + throw new InvalidOperationException ( + $"ComputedLayout for \"{superView}\": \"{from}\" linked with \"{to}\" was not found. Did you forget to add it to {superView}?"); + } + } + + // return L (a topologically sorted order) + return result; + } // TopologicalSort + + /// Determines if the View's can be set to a new value. + /// + /// + /// Contains the width that would result if were set to + /// "/> + /// + /// + /// if the View's can be changed to the specified value. False + /// otherwise. + /// + internal bool TrySetHeight (int desiredHeight, out int resultHeight) + { + int h = desiredHeight; + bool canSetHeight; + + switch (Height) + { + case Dim.DimCombine _: + case Dim.DimView _: + case Dim.DimFill _: + // It's a Dim.DimCombine and so can't be assigned. Let it have it's height anchored. + h = Height.Anchor (h); + canSetHeight = !ValidatePosDim; + + break; + case Dim.DimFactor factor: + // Tries to get the SuperView height otherwise the view height. + int sh = SuperView != null ? SuperView.Frame.Height : h; + + if (factor.IsFromRemaining ()) + { + sh -= Frame.Y; + } + + h = Height.Anchor (sh); + canSetHeight = !ValidatePosDim; + + break; + default: + canSetHeight = true; + + break; + } + + resultHeight = h; + + return canSetHeight; + } + + /// Determines if the View's can be set to a new value. + /// + /// + /// Contains the width that would result if were set to + /// "/> + /// + /// + /// if the View's can be changed to the specified value. False + /// otherwise. + /// + internal bool TrySetWidth (int desiredWidth, out int resultWidth) + { + int w = desiredWidth; + bool canSetWidth; + + switch (Width) + { + case Dim.DimCombine _: + case Dim.DimView _: + case Dim.DimFill _: + // It's a Dim.DimCombine and so can't be assigned. Let it have it's Width anchored. + w = Width.Anchor (w); + canSetWidth = !ValidatePosDim; + + break; + case Dim.DimFactor factor: + // Tries to get the SuperView Width otherwise the view Width. + int sw = SuperView != null ? SuperView.Frame.Width : w; + + if (factor.IsFromRemaining ()) + { + sw -= Frame.X; + } + + w = Width.Anchor (sw); + canSetWidth = !ValidatePosDim; + + break; + default: + canSetWidth = true; + + break; + } + + resultWidth = w; + + return canSetWidth; + } + + /// + /// Throws an if any SubViews are using Dim objects that depend on this Views + /// dimensions. + /// + /// + private void CheckDimAuto () + { + if (!ValidatePosDim || !IsInitialized || (Width is not Dim.DimAuto && Height is not Dim.DimAuto)) + { + return; + } + + void ThrowInvalid (View view, object checkPosDim, string name) + { + // TODO: Figure out how to make CheckDimAuto deal with PosCombine + object bad = null; + + switch (checkPosDim) + { + case Pos pos and not Pos.PosAbsolute and not Pos.PosView and not Pos.PosCombine: + bad = pos; + + break; + case Pos pos and Pos.PosCombine: + // Recursively check for not Absolute or not View + ThrowInvalid (view, (pos as Pos.PosCombine)._left, name); + ThrowInvalid (view, (pos as Pos.PosCombine)._right, name); + + break; + + case Dim dim and not Dim.DimAbsolute and not Dim.DimView and not Dim.DimCombine: + bad = dim; + + break; + case Dim dim and Dim.DimCombine: + // Recursively check for not Absolute or not View + ThrowInvalid (view, (dim as Dim.DimCombine)._left, name); + ThrowInvalid (view, (dim as Dim.DimCombine)._right, name); + + break; + } + + if (bad != null) + { + throw new InvalidOperationException ( + @$"{view.GetType ().Name}.{name} = {bad.GetType ().Name} which depends on the SuperView's dimensions and the SuperView uses Dim.Auto."); + } + } + + // Verify none of the subviews are using Dim objects that depend on the SuperView's dimensions. + foreach (View view in Subviews) + { + if (Width is Dim.DimAuto { _min: null }) + { + ThrowInvalid (view, view.Width, nameof (view.Width)); + ThrowInvalid (view, view.X, nameof (view.X)); + } + + if (Height is Dim.DimAuto { _min: null }) + { + ThrowInvalid (view, view.Height, nameof (view.Height)); + ThrowInvalid (view, view.Y, nameof (view.Y)); + } + } + } + + private void LayoutSubview (View v, Rect contentArea) + { + //if (v.LayoutStyle == LayoutStyle.Computed) { + v.SetRelativeLayout (contentArea); + + //} + + v.LayoutSubviews (); + v.LayoutNeeded = false; + } + + /// Resizes the View to fit the specified size. Factors in the HotKey. + /// + /// whether the Bounds was changed or not + private bool ResizeBoundsToFit (Size size) + { + var boundsChanged = false; + bool canSizeW = TrySetWidth (size.Width - GetHotKeySpecifierLength (), out int rW); + bool canSizeH = TrySetHeight (size.Height - GetHotKeySpecifierLength (false), out int rH); + + if (canSizeW) + { + boundsChanged = true; + _width = rW; + } + + if (canSizeH) + { + boundsChanged = true; + _height = rH; + } + + if (boundsChanged) + { + Bounds = new Rect (Bounds.X, Bounds.Y, canSizeW ? rW : Bounds.Width, canSizeH ? rH : Bounds.Height); + } + + return boundsChanged; + } + + private bool ResizeView (bool autoSize) + { + if (!autoSize) + { + return false; + } + + var boundsChanged = true; + Size newFrameSize = GetTextAutoSize (); + + if (IsInitialized && newFrameSize != Frame.Size) + { + if (ValidatePosDim) + { + // BUGBUG: This ain't right, obviously. We need to figure out how to handle this. + boundsChanged = ResizeBoundsToFit (newFrameSize); + } + else + { + Height = newFrameSize.Height; + Width = newFrameSize.Width; + } + } + + return boundsChanged; + } + + // Diagnostics to highlight when X or Y is read before the view has been initialized + private Pos VerifyIsInitialized (Pos pos, string member) + { #if DEBUG - if (LayoutStyle == LayoutStyle.Computed && !IsInitialized) { - Debug.WriteLine ($"WARNING: \"{this}\" has not been initialized; {member} is indeterminate {pos}. This is potentially a bug."); - } + if (LayoutStyle == LayoutStyle.Computed && !IsInitialized) + { + Debug.WriteLine ($"WARNING: \"{this}\" has not been initialized; {member} is indeterminate {pos}. This is potentially a bug."); + } #endif // DEBUG - return pos; - } + return pos; + } - // Diagnostics to highlight when Width or Height is read before the view has been initialized - Dim VerifyIsInitialized (Dim dim, string member) - { + // Diagnostics to highlight when Width or Height is read before the view has been initialized + private Dim VerifyIsInitialized (Dim dim, string member) + { #if DEBUG - if (LayoutStyle == LayoutStyle.Computed && !IsInitialized) { - Debug.WriteLine ($"WARNING: \"{this}\" has not been initialized; {member} is indeterminate: {dim}. This is potentially a bug."); - } + if (LayoutStyle == LayoutStyle.Computed && !IsInitialized) + { + Debug.WriteLine ($"WARNING: \"{this}\" has not been initialized; {member} is indeterminate: {dim}. This is potentially a bug."); + } #endif // DEBUG - return dim; - } - - /// - /// Called whenever the view needs to be resized. This is called whenever , - /// , , , or changes. - /// - /// - /// - /// Determines the relative bounds of the and its s, and then calls - /// to update the view. - /// - /// - internal void OnResizeNeeded () - { - // TODO: Identify a real-world use-case where this API should be virtual. - // TODO: Until then leave it `internal` and non-virtual - // First try SuperView.Bounds, then Application.Top, then Driver.Bounds. - // Finally, if none of those are valid, use int.MaxValue (for Unit tests). - var relativeBounds = SuperView is { IsInitialized: true } ? SuperView.Bounds : - Application.Top != null && Application.Top.IsInitialized ? Application.Top.Bounds : - Application.Driver?.Bounds ?? - new Rect (0, 0, int.MaxValue, int.MaxValue); - SetRelativeLayout (relativeBounds); - - // TODO: Determine what, if any of the below is actually needed here. - if (IsInitialized) { - SetFrameToFitText (); - LayoutAdornments (); - SetTextFormatterSize (); - SetNeedsLayout (); - SetNeedsDisplay (); - } - } - - /// - /// Sets the internal flag for this View and all of it's - /// subviews and it's SuperView. The main loop will call SetRelativeLayout and LayoutSubviews - /// for any view with set. - /// - internal void SetNeedsLayout () - { - if (LayoutNeeded) { - return; - } - LayoutNeeded = true; - foreach (var view in Subviews) { - view.SetNeedsLayout (); - } - TextFormatter.NeedsFormat = true; - SuperView?.SetNeedsLayout (); - } - - /// - /// Indicates that the view does not need to be laid out. - /// - protected void ClearLayoutNeeded () => LayoutNeeded = false; - - /// - /// Converts a screen-relative coordinate to a Frame-relative coordinate. Frame-relative means - /// relative to the View's 's . - /// - /// The coordinate relative to the 's . - /// Screen-relative column. - /// Screen-relative row. - public Point ScreenToFrame (int x, int y) - { - var superViewBoundsOffset = SuperView?.GetBoundsOffset () ?? Point.Empty; - var ret = new Point (x - Frame.X - superViewBoundsOffset.X, y - Frame.Y - superViewBoundsOffset.Y); - if (SuperView != null) { - var superFrame = SuperView.ScreenToFrame (x - superViewBoundsOffset.X, y - superViewBoundsOffset.Y); - ret = new Point (superFrame.X - Frame.X, superFrame.Y - Frame.Y); - } - return ret; - } - - /// - /// Converts a screen-relative coordinate to a bounds-relative coordinate. - /// - /// The coordinate relative to this view's . - /// Screen-relative column. - /// Screen-relative row. - public Point ScreenToBounds (int x, int y) - { - var screen = ScreenToFrame (x, y); - var boundsOffset = GetBoundsOffset (); - return new Point (screen.X - boundsOffset.X, screen.Y - boundsOffset.Y); - } - - /// - /// Converts a -relative coordinate to a screen-relative coordinate. The output is optionally clamped - /// to the screen dimensions. - /// - /// -relative column. - /// -relative row. - /// Absolute column; screen-relative. - /// Absolute row; screen-relative. - /// - /// If , and will be clamped to the - /// screen dimensions (will never be negative and will always be less than and - /// , respectively. - /// - public virtual void BoundsToScreen (int x, int y, out int rx, out int ry, bool clamped = true) - { - var boundsOffset = GetBoundsOffset (); - rx = x + Frame.X + boundsOffset.X; - ry = y + Frame.Y + boundsOffset.Y; - - var super = SuperView; - while (super != null) { - boundsOffset = super.GetBoundsOffset (); - rx += super.Frame.X + boundsOffset.X; - ry += super.Frame.Y + boundsOffset.Y; - super = super.SuperView; - } - - // The following ensures that the cursor is always in the screen boundaries. - if (clamped) { - ry = Math.Min (ry, Driver.Rows - 1); - rx = Math.Min (rx, Driver.Cols - 1); - } - } - - /// - /// Converts a -relative region to a screen-relative region. - /// - public Rect BoundsToScreen (Rect region) - { - BoundsToScreen (region.X, region.Y, out var x, out var y, false); - return new Rect (x, y, region.Width, region.Height); - } - - /// - /// Gets the with a screen-relative location. - /// - /// The location and size of the view in screen-relative coordinates. - public virtual Rect FrameToScreen () - { - var ret = Frame; - var super = SuperView; - while (super != null) { - var boundsOffset = super.GetBoundsOffset (); - ret.X += super.Frame.X + boundsOffset.X; - ret.Y += super.Frame.Y + boundsOffset.Y; - super = super.SuperView; - } - return ret; - } - - /// - /// Applies the view's position (, ) and dimension (, and - /// ) to - /// , given a rectangle describing the SuperView's Bounds (nominally the same as - /// this.SuperView.Bounds). - /// - /// - /// The rectangle describing the SuperView's Bounds (nominally the same as - /// this.SuperView.Bounds). - /// - internal void SetRelativeLayout (Rect superviewBounds) - { - Debug.Assert (_x != null); - Debug.Assert (_y != null); - Debug.Assert (_width != null); - Debug.Assert (_height != null); - - CheckDimAuto (); - - int newX, newW, newY, newH; - var autosize = Size.Empty; - - if (AutoSize) { - // Note this is global to this function and used as such within the local functions defined - // below. In v2 AutoSize will be re-factored to not need to be dealt with in this function. - autosize = GetTextAutoSize (); - } - - // TODO: Since GetNewLocationAndDimension does not depend on View, it can be moved into PosDim.cs - // TODO: to make architecture more clean. Do this after DimAuto is implemented and the - // TODO: View.AutoSize stuff is removed. - - // Returns the new dimension (width or height) and location (x or y) for the View given - // the superview's Bounds - // the current Pos (View.X or View.Y) - // the current Dim (View.Width or View.Height) - // This method is called recursively if pos is Pos.PosCombine - (int newLocation, int newDimension) GetNewLocationAndDimension (bool width, Rect superviewBounds, Pos pos, Dim dim, int autosizeDimension) - { - // Gets the new dimension (width or height, dependent on `width`) of the given Dim given: - // location: the current location (x or y) - // dimension: the new dimension (width or height) (if relevant for Dim type) - // autosize: the size to use if autosize = true - // This method is recursive if d is Dim.DimCombine - int GetNewDimension (Dim d, int location, int dimension, int autosize) - { - int newDimension; - switch (d) { - - case Dim.DimCombine combine: - // TODO: Move combine logic into DimCombine? - var leftNewDim = GetNewDimension (combine._left, location, dimension, autosize); - var rightNewDim = GetNewDimension (combine._right, location, dimension, autosize); - if (combine._add) { - newDimension = leftNewDim + rightNewDim; - } else { - newDimension = leftNewDim - rightNewDim; - } - newDimension = AutoSize && autosize > newDimension ? autosize : newDimension; - break; - - case Dim.DimFactor factor when !factor.IsFromRemaining (): - newDimension = d.Anchor (dimension); - newDimension = AutoSize && autosize > newDimension ? autosize : newDimension; - break; - - case Dim.DimAuto auto: - var thickness = GetAdornmentsThickness (); - var text = 0; - var subviews = 0; - - if (auto._style is Dim.DimAutoStyle.Text or Dim.DimAutoStyle.Auto) { - if (Id == "vlabel") { - - } - - text = int.Max (width ? TextFormatter.Size.Width : TextFormatter.Size.Height, - auto._min?.Anchor (width ? superviewBounds.Width : superviewBounds.Height) ?? 0); - } - - if (auto._style is Dim.DimAutoStyle.Subviews or Dim.DimAutoStyle.Auto) { - subviews = Subviews.Count == 0 ? 0 : Subviews - .Where (v => width ? v.X is not Pos.PosAnchorEnd : v.Y is not Pos.PosAnchorEnd) - .Max (v => width ? v.Frame.X + v.Frame.Width : v.Frame.Y + v.Frame.Height); - } - - var max = int.Max (text, subviews); - newDimension = int.Max (width ? max + thickness.Left + thickness.Right : max + thickness.Top + thickness.Bottom, - auto._min?.Anchor (width ? superviewBounds.Width : superviewBounds.Height) ?? 0); - break; - - case Dim.DimAbsolute: - // DimAbsoulte.Anchor (int width) ignores width and returns n - newDimension = Math.Max (d.Anchor (0), 0); - newDimension = AutoSize && autosize > newDimension ? autosize : newDimension; - break; - - case Dim.DimFill: - default: - newDimension = Math.Max (d.Anchor (dimension - location), 0); - newDimension = AutoSize && autosize > newDimension ? autosize : newDimension; - break; - } - - return newDimension; - } - - int newDimension, newLocation; - var superviewDimension = width ? superviewBounds.Width : superviewBounds.Height; - - // Determine new location - switch (pos) { - case Pos.PosCenter posCenter: - // For Center, the dimension is dependent on location, but we need to force getting the dimension first - // using a location of 0 - newDimension = Math.Max (GetNewDimension (dim, 0, superviewDimension, autosizeDimension), 0); - newLocation = posCenter.Anchor (superviewDimension - newDimension); - newDimension = Math.Max (GetNewDimension (dim, newLocation, superviewDimension, autosizeDimension), 0); - break; - - case Pos.PosCombine combine: - // TODO: Move combine logic into PosCombine? - // TODO: Move combine logic into PosCombine? - int left, right; - (left, newDimension) = GetNewLocationAndDimension (width, superviewBounds, combine._left, dim, autosizeDimension); - (right, newDimension) = GetNewLocationAndDimension (width, superviewBounds, combine._right, dim, autosizeDimension); - if (combine._add) { - newLocation = left + right; - } else { - newLocation = left - right; - } - newDimension = Math.Max (GetNewDimension (dim, newLocation, superviewDimension, autosizeDimension), 0); - break; - - case Pos.PosAnchorEnd: - case Pos.PosAbsolute: - case Pos.PosFactor: - case Pos.PosFunc: - case Pos.PosView: - default: - newLocation = pos?.Anchor (superviewDimension) ?? 0; - newDimension = Math.Max (GetNewDimension (dim, newLocation, superviewDimension, autosizeDimension), 0); - break; - } - - - return (newLocation, newDimension); - } - - // horizontal/width - (newX, newW) = GetNewLocationAndDimension (true, superviewBounds, _x, _width, autosize.Width); - - // vertical/height - (newY, newH) = GetNewLocationAndDimension (false, superviewBounds, _y, _height, autosize.Height); - - var r = new Rect (newX, newY, newW, newH); - if (Frame != r) { - // Set the frame. Do NOT use `Frame` as it overwrites X, Y, Width, and Height, making - // the view LayoutStyle.Absolute. - _frame = r; - if (_x is Pos.PosAbsolute) { - _x = Frame.X; - } - if (_y is Pos.PosAbsolute) { - _y = Frame.Y; - } - if (_width is Dim.DimAbsolute) { - _width = Frame.Width; - } - if (_height is Dim.DimAbsolute) { - _height = Frame.Height; - } - - if (IsInitialized) { - // TODO: Figure out what really is needed here. All unit tests (except AutoSize) pass as-is - SetTextFormatterSize (); - SetNeedsLayout (); - } - - // BUGBUG: Why is this AFTER setting Frame? Seems duplicative. - if (!SetFrameToFitText ()) { - SetTextFormatterSize (); - } - } - } - - /// - /// Fired after the View's method has completed. - /// - /// - /// Subscribe to this event to perform tasks when the has been resized or the layout has otherwise - /// changed. - /// - public event EventHandler LayoutStarted; - - /// - /// Raises the event. Called from before any subviews have been - /// laid out. - /// - internal virtual void OnLayoutStarted (LayoutEventArgs args) => LayoutStarted?.Invoke (this, args); - - /// - /// Fired after the View's method has completed. - /// - /// - /// Subscribe to this event to perform tasks when the has been resized or the layout has otherwise - /// changed. - /// - public event EventHandler LayoutComplete; - - /// - /// Raises the event. Called from before all sub-views have been - /// laid out. - /// - internal virtual void OnLayoutComplete (LayoutEventArgs args) => LayoutComplete?.Invoke (this, args); - - internal void CollectPos (Pos pos, View from, ref HashSet nNodes, ref HashSet<(View, View)> nEdges) - { - switch (pos) { - case Pos.PosView pv: - // See #2461 - //if (!from.InternalSubviews.Contains (pv.Target)) { - // throw new InvalidOperationException ($"View {pv.Target} is not a subview of {from}"); - //} - if (pv.Target != this) { - nEdges.Add ((pv.Target, from)); - } - return; - case Pos.PosCombine pc: - CollectPos (pc._left, from, ref nNodes, ref nEdges); - CollectPos (pc._right, from, ref nNodes, ref nEdges); - break; - } - } - - internal void CollectDim (Dim dim, View from, ref HashSet nNodes, ref HashSet<(View, View)> nEdges) - { - switch (dim) { - case Dim.DimView dv: - // See #2461 - //if (!from.InternalSubviews.Contains (dv.Target)) { - // throw new InvalidOperationException ($"View {dv.Target} is not a subview of {from}"); - //} - if (dv.Target != this) { - nEdges.Add ((dv.Target, from)); - } - return; - case Dim.DimCombine dc: - CollectDim (dc._left, from, ref nNodes, ref nEdges); - CollectDim (dc._right, from, ref nNodes, ref nEdges); - break; - } - } - - internal void CollectAll (View from, ref HashSet nNodes, ref HashSet<(View, View)> nEdges) - { - // BUGBUG: This should really only work on initialized subviews - foreach (var v in from.InternalSubviews /*.Where(v => v.IsInitialized)*/) { - nNodes.Add (v); - if (v.LayoutStyle != LayoutStyle.Computed) { - continue; - } - CollectPos (v.X, v, ref nNodes, ref nEdges); - CollectPos (v.Y, v, ref nNodes, ref nEdges); - CollectDim (v.Width, v, ref nNodes, ref nEdges); - CollectDim (v.Height, v, ref nNodes, ref nEdges); - } - } - - // https://en.wikipedia.org/wiki/Topological_sorting - internal static List TopologicalSort (View superView, IEnumerable nodes, ICollection<(View From, View To)> edges) - { - var result = new List (); - - // Set of all nodes with no incoming edges - var noEdgeNodes = new HashSet (nodes.Where (n => edges.All (e => !e.To.Equals (n)))); - - while (noEdgeNodes.Any ()) { - // remove a node n from S - var n = noEdgeNodes.First (); - noEdgeNodes.Remove (n); - - // add n to tail of L - if (n != superView) { - result.Add (n); - } - - // for each node m with an edge e from n to m do - foreach (var e in edges.Where (e => e.From.Equals (n)).ToArray ()) { - var m = e.To; - - // remove edge e from the graph - edges.Remove (e); - - // if m has no other incoming edges then - if (edges.All (me => !me.To.Equals (m)) && m != superView) { - // insert m into S - noEdgeNodes.Add (m); - } - } - } - - if (!edges.Any ()) { - return result; - } - - foreach ((var from, var to) in edges) { - if (from == to) { - // if not yet added to the result, add it and remove from edge - if (result.Find (v => v == from) == null) { - result.Add (from); - } - edges.Remove ((from, to)); - } else if (from.SuperView == to.SuperView) { - // if 'from' is not yet added to the result, add it - if (result.Find (v => v == from) == null) { - result.Add (from); - } - // if 'to' is not yet added to the result, add it - if (result.Find (v => v == to) == null) { - result.Add (to); - } - // remove from edge - edges.Remove ((from, to)); - } else if (from != superView?.GetTopSuperView (to, from) && !ReferenceEquals (from, to)) { - if (ReferenceEquals (from.SuperView, to)) { - throw new InvalidOperationException ($"ComputedLayout for \"{superView}\": \"{to}\" references a SubView (\"{from}\")."); - } - throw new InvalidOperationException ($"ComputedLayout for \"{superView}\": \"{from}\" linked with \"{to}\" was not found. Did you forget to add it to {superView}?"); - } - } - // return L (a topologically sorted order) - return result; - } // TopologicalSort - - /// - /// Overriden by to do nothing, as the does not have adornments. - /// - internal virtual void LayoutAdornments () - { - if (Margin == null) { - return; // CreateAdornments () has not been called yet - } - - if (Margin.Frame.Size != Frame.Size) { - Margin._frame = new Rect (Point.Empty, Frame.Size); - Margin.X = 0; - Margin.Y = 0; - Margin.Width = Frame.Size.Width; - Margin.Height = Frame.Size.Height; - Margin.SetNeedsLayout (); - Margin.SetNeedsDisplay (); - } - - var border = Margin.Thickness.GetInside (Margin.Frame); - if (border != Border.Frame) { - Border._frame = new Rect (new Point (border.Location.X, border.Location.Y), border.Size); - Border.X = border.Location.X; - Border.Y = border.Location.Y; - Border.Width = border.Size.Width; - Border.Height = border.Size.Height; - Border.SetNeedsLayout (); - Border.SetNeedsDisplay (); - } - - var padding = Border.Thickness.GetInside (Border.Frame); - if (padding != Padding.Frame) { - Padding._frame = new Rect (new Point (padding.Location.X, padding.Location.Y), padding.Size); - Padding.X = padding.Location.X; - Padding.Y = padding.Location.Y; - Padding.Width = padding.Size.Width; - Padding.Height = padding.Size.Height; - Padding.SetNeedsLayout (); - Padding.SetNeedsDisplay (); - } - } - - /// - /// Invoked when a view starts executing or when the dimensions of the view have changed, for example in - /// response to the container view or terminal resizing. - /// - /// - /// - /// The position and dimensions of the view are indeterminate until the view has been initialized. Therefore, - /// the behavior of this method is indeterminate if is . - /// - /// - /// Raises the event) before it returns. - /// - /// - public virtual void LayoutSubviews () - { - if (!IsInitialized) { - Debug.WriteLine ($"WARNING: LayoutSubviews called before view has been initialized. This is likely a bug in {this}"); - } - - if (!LayoutNeeded) { - return; - } - - CheckDimAuto (); - - LayoutAdornments (); - - var oldBounds = Bounds; - OnLayoutStarted (new LayoutEventArgs { OldBounds = oldBounds }); - - SetTextFormatterSize (); - - // Sort out the dependencies of the X, Y, Width, Height properties - var nodes = new HashSet (); - var edges = new HashSet<(View, View)> (); - CollectAll (this, ref nodes, ref edges); - var ordered = TopologicalSort (SuperView, nodes, edges); - foreach (var v in ordered) { - if (v.Width is Dim.DimAuto || v.Height is Dim.DimAuto) { - // If the view is auto-sized... - var f = v.Frame; - v._frame = new Rect (v.Frame.X, v.Frame.Y, 0, 0); - LayoutSubview (v, new Rect (GetBoundsOffset (), Bounds.Size)); - if (v.Frame != f) { - // The subviews changed; do it again - v.LayoutNeeded = true; - LayoutSubview (v, new Rect (GetBoundsOffset (), Bounds.Size)); - } - } else { - LayoutSubview (v, new Rect (GetBoundsOffset (), Bounds.Size)); - } - } - - // If the 'to' is rooted to 'from' and the layoutstyle is Computed it's a special-case. - // Use LayoutSubview with the Frame of the 'from' - if (SuperView != null && GetTopSuperView () != null && LayoutNeeded && edges.Count > 0) { - foreach ((var from, var to) in edges) { - LayoutSubview (to, from.Frame); - } - } - - LayoutNeeded = false; - - OnLayoutComplete (new LayoutEventArgs { OldBounds = oldBounds }); - } - - void LayoutSubview (View v, Rect contentArea) - { - //if (v.LayoutStyle == LayoutStyle.Computed) { - v.SetRelativeLayout (contentArea); - //} - - v.LayoutSubviews (); - v.LayoutNeeded = false; - } - - bool ResizeView (bool autoSize) - { - if (!autoSize) { - return false; - } - - var boundsChanged = true; - var newFrameSize = GetTextAutoSize (); - if (IsInitialized && newFrameSize != Frame.Size) { - if (ValidatePosDim) { - // BUGBUG: This ain't right, obviously. We need to figure out how to handle this. - boundsChanged = ResizeBoundsToFit (newFrameSize); - } else { - Height = newFrameSize.Height; - Width = newFrameSize.Width; - } - } - return boundsChanged; - } - - /// - /// Resizes the View to fit the specified size. Factors in the HotKey. - /// - /// - /// whether the Bounds was changed or not - bool ResizeBoundsToFit (Size size) - { - var boundsChanged = false; - var canSizeW = TrySetWidth (size.Width - GetHotKeySpecifierLength (), out var rW); - var canSizeH = TrySetHeight (size.Height - GetHotKeySpecifierLength (false), out var rH); - if (canSizeW) { - boundsChanged = true; - _width = rW; - } - if (canSizeH) { - boundsChanged = true; - _height = rH; - } - if (boundsChanged) { - Bounds = new Rect (Bounds.X, Bounds.Y, canSizeW ? rW : Bounds.Width, canSizeH ? rH : Bounds.Height); - } - - return boundsChanged; - } - - - /// - /// Determines if the View's can be set to a new value. - /// - /// - /// - /// Contains the width that would result if were set to - /// "/> - /// - /// - /// if the View's can be changed to the specified value. False - /// otherwise. - /// - internal bool TrySetWidth (int desiredWidth, out int resultWidth) - { - var w = desiredWidth; - bool canSetWidth; - switch (Width) { - case Dim.DimCombine _: - case Dim.DimView _: - case Dim.DimFill _: - // It's a Dim.DimCombine and so can't be assigned. Let it have it's Width anchored. - w = Width.Anchor (w); - canSetWidth = !ValidatePosDim; - break; - case Dim.DimFactor factor: - // Tries to get the SuperView Width otherwise the view Width. - var sw = SuperView != null ? SuperView.Frame.Width : w; - if (factor.IsFromRemaining ()) { - sw -= Frame.X; - } - w = Width.Anchor (sw); - canSetWidth = !ValidatePosDim; - break; - default: - canSetWidth = true; - break; - } - resultWidth = w; - - return canSetWidth; - } - - /// - /// Determines if the View's can be set to a new value. - /// - /// - /// - /// Contains the width that would result if were set to - /// "/> - /// - /// - /// if the View's can be changed to the specified value. False - /// otherwise. - /// - internal bool TrySetHeight (int desiredHeight, out int resultHeight) - { - var h = desiredHeight; - bool canSetHeight; - switch (Height) { - case Dim.DimCombine _: - case Dim.DimView _: - case Dim.DimFill _: - // It's a Dim.DimCombine and so can't be assigned. Let it have it's height anchored. - h = Height.Anchor (h); - canSetHeight = !ValidatePosDim; - break; - case Dim.DimFactor factor: - // Tries to get the SuperView height otherwise the view height. - var sh = SuperView != null ? SuperView.Frame.Height : h; - if (factor.IsFromRemaining ()) { - sh -= Frame.Y; - } - h = Height.Anchor (sh); - canSetHeight = !ValidatePosDim; - break; - default: - canSetHeight = true; - break; - } - resultHeight = h; - - return canSetHeight; - } - - /// - /// Finds which view that belong to the superview at the provided location. - /// - /// The superview where to look for. - /// The column location in the superview. - /// The row location in the superview. - /// The found view screen relative column location. - /// The found view screen relative row location. - /// - /// The view that was found at the and coordinates. - /// if no view was found. - /// - public static View FindDeepestView (View start, int x, int y, out int resx, out int resy) - { - resy = resx = 0; - if (start == null || !start.Frame.Contains (x, y)) { - return null; - } - - var startFrame = start.Frame; - if (start.InternalSubviews != null) { - var count = start.InternalSubviews.Count; - if (count > 0) { - var boundsOffset = start.GetBoundsOffset (); - var rx = x - (startFrame.X + boundsOffset.X); - var ry = y - (startFrame.Y + boundsOffset.Y); - for (var i = count - 1; i >= 0; i--) { - var v = start.InternalSubviews [i]; - if (v.Visible && v.Frame.Contains (rx, ry)) { - var deep = FindDeepestView (v, rx, ry, out resx, out resy); - if (deep == null) { - return v; - } - return deep; - } - } - } - } - resx = x - startFrame.X; - resy = y - startFrame.Y; - return start; - } -} \ No newline at end of file + return dim; + } +} diff --git a/Terminal.Gui/View/ViewSubViews.cs b/Terminal.Gui/View/ViewSubViews.cs index 4cbc95c10..914e72ddd 100644 --- a/Terminal.Gui/View/ViewSubViews.cs +++ b/Terminal.Gui/View/ViewSubViews.cs @@ -1,725 +1,927 @@ -using System; -using System.Collections.Generic; - -namespace Terminal.Gui; - -public partial class View { - static readonly IList _empty = new List (0).AsReadOnly (); - - internal bool _addingView; - - List _subviews; // This is null, and allocated on demand. - - View _superView; - - /// - /// Returns the container for this view, or null if this view has not been added to a container. - /// - /// The super view. - public virtual View SuperView { - get => _superView; - set => throw new NotImplementedException (); - } - - /// - /// This returns a list of the subviews contained by this view. - /// - /// The subviews. - public IList Subviews => _subviews?.AsReadOnly () ?? _empty; - - // Internally, we use InternalSubviews rather than subviews, as we do not expect us - // to make the same mistakes our users make when they poke at the Subviews. - internal IList InternalSubviews => _subviews ?? _empty; - - /// - /// Returns a value indicating if this View is currently on Top (Active) - /// - public bool IsCurrentTop => Application.Current == this; - - /// - /// Indicates whether the view was added to . - /// - public bool IsAdded { get; private set; } - - /// - /// Event fired when this view is added to another. - /// - public event EventHandler Added; - - /// - /// Adds a subview (child) to this view. - /// - /// - /// The Views that have been added to this view can be retrieved via the property. - /// See also - /// - public virtual void Add (View view) - { - if (view == null) { - return; - } - if (_subviews == null) { - _subviews = new List (); - } - if (_tabIndexes == null) { - _tabIndexes = new List (); - } - _subviews.Add (view); - _tabIndexes.Add (view); - view._superView = this; - if (view.CanFocus) { - _addingView = true; - if (SuperView?.CanFocus == false) { - SuperView._addingView = true; - SuperView.CanFocus = true; - SuperView._addingView = false; - } - CanFocus = true; - view._tabIndex = _tabIndexes.IndexOf (view); - _addingView = false; - } - if (view.Enabled && !Enabled) { - view._oldEnabled = true; - view.Enabled = false; - } - - OnAdded (new SuperViewChangedEventArgs (this, view)); - if (IsInitialized && !view.IsInitialized) { - view.BeginInit (); - view.EndInit (); - } - CheckDimAuto (); - SetNeedsLayout (); - SetNeedsDisplay (); - } - - /// - /// Adds the specified views (children) to the view. - /// - /// Array of one or more views (can be optional parameter). - /// - /// The Views that have been added to this view can be retrieved via the property. - /// See also - /// - public void Add (params View [] views) - { - if (views == null) { - return; - } - foreach (var view in views) { - Add (view); - } - } - - /// - /// Method invoked when a subview is being added to this view. - /// - /// Event where is the subview being added. - public virtual void OnAdded (SuperViewChangedEventArgs e) - { - var view = e.Child; - view.IsAdded = true; - view.OnResizeNeeded (); - view.Added?.Invoke (this, e); - } - - /// - /// Event fired when this view is removed from another. - /// - public event EventHandler Removed; - - /// - /// Removes all subviews (children) added via or from this View. - /// - public virtual void RemoveAll () - { - if (_subviews == null) { - return; - } - - while (_subviews.Count > 0) { - Remove (_subviews [0]); - } - } - - /// - /// Removes a subview added via or from this View. - /// - /// - /// - public virtual void Remove (View view) - { - if (view == null || _subviews == null) { - return; - } - - var touched = view.Frame; - _subviews.Remove (view); - _tabIndexes.Remove (view); - view._superView = null; - view._tabIndex = -1; - SetNeedsLayout (); - SetNeedsDisplay (); - - foreach (var v in _subviews) { - if (v.Frame.IntersectsWith (touched)) { - view.SetNeedsDisplay (); - } - } - OnRemoved (new SuperViewChangedEventArgs (this, view)); - if (Focused == view) { - Focused = null; - } - } - - /// - /// Method invoked when a subview is being removed from this view. - /// - /// Event args describing the subview being removed. - public virtual void OnRemoved (SuperViewChangedEventArgs e) - { - var view = e.Child; - view.IsAdded = false; - view.Removed?.Invoke (this, e); - } - - - void PerformActionForSubview (View subview, Action action) - { - if (_subviews.Contains (subview)) { - action (subview); - } - - SetNeedsDisplay (); - subview.SetNeedsDisplay (); - } - - /// - /// Brings the specified subview to the front so it is drawn on top of any other views. - /// - /// The subview to send to the front - /// - /// . - /// - public void BringSubviewToFront (View subview) => PerformActionForSubview (subview, x => { - _subviews.Remove (x); - _subviews.Add (x); - }); - - /// - /// Sends the specified subview to the front so it is the first view drawn - /// - /// The subview to send to the front - /// - /// . - /// - public void SendSubviewToBack (View subview) => PerformActionForSubview (subview, x => { - _subviews.Remove (x); - _subviews.Insert (0, subview); - }); - - /// - /// Moves the subview backwards in the hierarchy, only one step - /// - /// The subview to send backwards - /// - /// If you want to send the view all the way to the back use SendSubviewToBack. - /// - public void SendSubviewBackwards (View subview) => PerformActionForSubview (subview, x => { - var idx = _subviews.IndexOf (x); - if (idx > 0) { - _subviews.Remove (x); - _subviews.Insert (idx - 1, x); - } - }); - - /// - /// Moves the subview backwards in the hierarchy, only one step - /// - /// The subview to send backwards - /// - /// If you want to send the view all the way to the back use SendSubviewToBack. - /// - public void BringSubviewForward (View subview) => PerformActionForSubview (subview, x => { - var idx = _subviews.IndexOf (x); - if (idx + 1 < _subviews.Count) { - _subviews.Remove (x); - _subviews.Insert (idx + 1, x); - } - }); - - /// - /// Get the top superview of a given . - /// - /// The superview view. - public View GetTopSuperView (View view = null, View superview = null) - { - var top = superview ?? Application.Top; - for (var v = view?.SuperView ?? this?.SuperView; v != null; v = v.SuperView) { - top = v; - if (top == superview) { - break; - } - } - - return top; - } - - - - #region Focus - internal enum Direction { - Forward, - Backward - } - - /// - /// Event fired when the view gets focus. - /// - public event EventHandler Enter; - - /// - /// Event fired when the view looses focus. - /// - public event EventHandler Leave; - - Direction _focusDirection; - - internal Direction FocusDirection { - get => SuperView?.FocusDirection ?? _focusDirection; - set { - if (SuperView != null) { - SuperView.FocusDirection = value; - } else { - _focusDirection = value; - } - } - } - - - // BUGBUG: v2 - Seems weird that this is in View and not Responder. - bool _hasFocus; - - /// - public override bool HasFocus => _hasFocus; - - void SetHasFocus (bool value, View view, bool force = false) - { - if (_hasFocus != value || force) { - _hasFocus = value; - if (value) { - OnEnter (view); - } else { - OnLeave (view); - } - SetNeedsDisplay (); - } - - // Remove focus down the chain of subviews if focus is removed - if (!value && Focused != null) { - var f = Focused; - f.OnLeave (view); - f.SetHasFocus (false, view); - Focused = null; - } - } - - /// - /// Event fired when the value is being changed. - /// - public event EventHandler CanFocusChanged; - - /// - public override void OnCanFocusChanged () => CanFocusChanged?.Invoke (this, EventArgs.Empty); - - bool _oldCanFocus; - - /// - public override bool CanFocus { - get => base.CanFocus; - set { - if (!_addingView && IsInitialized && SuperView?.CanFocus == false && value) { - throw new InvalidOperationException ("Cannot set CanFocus to true if the SuperView CanFocus is false!"); - } - if (base.CanFocus != value) { - base.CanFocus = value; - - switch (value) { - case false when _tabIndex > -1: - TabIndex = -1; - break; - case true when SuperView?.CanFocus == false && _addingView: - SuperView.CanFocus = true; - break; - } - - if (value && _tabIndex == -1) { - TabIndex = SuperView != null ? SuperView._tabIndexes.IndexOf (this) : -1; - } - TabStop = value; - - if (!value && SuperView?.Focused == this) { - SuperView.Focused = null; - } - if (!value && HasFocus) { - SetHasFocus (false, this); - SuperView?.EnsureFocus (); - if (SuperView != null && SuperView.Focused == null) { - SuperView.FocusNext (); - if (SuperView.Focused == null && Application.Current != null) { - Application.Current.FocusNext (); - } - Application.BringOverlappedTopToFront (); - } - } - if (_subviews != null && IsInitialized) { - foreach (var view in _subviews) { - if (view.CanFocus != value) { - if (!value) { - view._oldCanFocus = view.CanFocus; - view._oldTabIndex = view._tabIndex; - view.CanFocus = false; - view._tabIndex = -1; - } else { - if (_addingView) { - view._addingView = true; - } - view.CanFocus = view._oldCanFocus; - view._tabIndex = view._oldTabIndex; - view._addingView = false; - } - } - } - } - OnCanFocusChanged (); - SetNeedsDisplay (); - } - } - } - - - /// - public override bool OnEnter (View view) - { - var args = new FocusEventArgs (view); - Enter?.Invoke (this, args); - if (args.Handled) { - return true; - } - if (base.OnEnter (view)) { - return true; - } - - return false; - } - - /// - public override bool OnLeave (View view) - { - var args = new FocusEventArgs (view); - Leave?.Invoke (this, args); - if (args.Handled) { - return true; - } - if (base.OnLeave (view)) { - return true; - } - - Driver?.SetCursorVisibility (CursorVisibility.Invisible); - return false; - } - - /// - /// Returns the currently focused view inside this view, or null if nothing is focused. - /// - /// The focused. - public View Focused { get; private set; } - - /// - /// Returns the most focused view in the chain of subviews (the leaf view that has the focus). - /// - /// The most focused View. - public View MostFocused { - get { - if (Focused == null) { - return null; - } - var most = Focused.MostFocused; - if (most != null) { - return most; - } - return Focused; - } - } - - /// - /// Causes the specified subview to have focus. - /// - /// View. - void SetFocus (View view) - { - if (view == null) { - return; - } - //Console.WriteLine ($"Request to focus {view}"); - if (!view.CanFocus || !view.Visible || !view.Enabled) { - return; - } - if (Focused?._hasFocus == true && Focused == view) { - return; - } - if (Focused?._hasFocus == true && Focused?.SuperView == view || view == this) { - - if (!view._hasFocus) { - view._hasFocus = true; - } - return; - } - // Make sure that this view is a subview - View c; - for (c = view._superView; c != null; c = c._superView) { - if (c == this) { - break; - } - } - if (c == null) { - throw new ArgumentException ("the specified view is not part of the hierarchy of this view"); - } - - if (Focused != null) { - Focused.SetHasFocus (false, view); - } - - var f = Focused; - Focused = view; - Focused.SetHasFocus (true, f); - Focused.EnsureFocus (); - - // Send focus upwards - if (SuperView != null) { - SuperView.SetFocus (this); - } else { - SetFocus (this); - } - } - - /// - /// Causes the specified view and the entire parent hierarchy to have the focused order updated. - /// - public void SetFocus () - { - if (!CanBeVisible (this) || !Enabled) { - if (HasFocus) { - SetHasFocus (false, this); - } - return; - } - - if (SuperView != null) { - SuperView.SetFocus (this); - } else { - SetFocus (this); - } - } - - /// - /// Finds the first view in the hierarchy that wants to get the focus if nothing is currently focused, otherwise, does - /// nothing. - /// - public void EnsureFocus () - { - if (Focused == null && _subviews?.Count > 0) { - if (FocusDirection == Direction.Forward) { - FocusFirst (); - } else { - FocusLast (); - } - } - } - - /// - /// Focuses the first focusable subview if one exists. - /// - public void FocusFirst () - { - if (!CanBeVisible (this)) { - return; - } - - if (_tabIndexes == null) { - SuperView?.SetFocus (this); - return; - } - - foreach (var view in _tabIndexes) { - if (view.CanFocus && view._tabStop && view.Visible && view.Enabled) { - SetFocus (view); - return; - } - } - } - - /// - /// Focuses the last focusable subview if one exists. - /// - public void FocusLast () - { - if (!CanBeVisible (this)) { - return; - } - - if (_tabIndexes == null) { - SuperView?.SetFocus (this); - return; - } - - for (var i = _tabIndexes.Count; i > 0;) { - i--; - - var v = _tabIndexes [i]; - if (v.CanFocus && v._tabStop && v.Visible && v.Enabled) { - SetFocus (v); - return; - } - } - } - - /// - /// Focuses the previous view. - /// - /// if previous was focused, otherwise. - public bool FocusPrev () - { - if (!CanBeVisible (this)) { - return false; - } - - FocusDirection = Direction.Backward; - if (_tabIndexes == null || _tabIndexes.Count == 0) { - return false; - } - - if (Focused == null) { - FocusLast (); - return Focused != null; - } - - var focusedIdx = -1; - for (var i = _tabIndexes.Count; i > 0;) { - i--; - var w = _tabIndexes [i]; - - if (w.HasFocus) { - if (w.FocusPrev ()) { - return true; - } - focusedIdx = i; - continue; - } - if (w.CanFocus && focusedIdx != -1 && w._tabStop && w.Visible && w.Enabled) { - Focused.SetHasFocus (false, w); - - if (w.CanFocus && w._tabStop && w.Visible && w.Enabled) { - w.FocusLast (); - } - - SetFocus (w); - return true; - } - } - if (Focused != null) { - Focused.SetHasFocus (false, this); - Focused = null; - } - return false; - } - - /// - /// Focuses the next view. - /// - /// if next was focused, otherwise. - public bool FocusNext () - { - if (!CanBeVisible (this)) { - return false; - } - - FocusDirection = Direction.Forward; - if (_tabIndexes == null || _tabIndexes.Count == 0) { - return false; - } - - if (Focused == null) { - FocusFirst (); - return Focused != null; - } - var focusedIdx = -1; - for (var i = 0; i < _tabIndexes.Count; i++) { - var w = _tabIndexes [i]; - - if (w.HasFocus) { - if (w.FocusNext ()) { - return true; - } - focusedIdx = i; - continue; - } - if (w.CanFocus && focusedIdx != -1 && w._tabStop && w.Visible && w.Enabled) { - Focused.SetHasFocus (false, w); - - if (w.CanFocus && w._tabStop && w.Visible && w.Enabled) { - w.FocusFirst (); - } - - SetFocus (w); - return true; - } - } - if (Focused != null) { - Focused.SetHasFocus (false, this); - Focused = null; - } - return false; - } - - View GetMostFocused (View view) - { - if (view == null) { - return null; - } - - return view.Focused != null ? GetMostFocused (view.Focused) : view; - } - - /// - /// Positions the cursor in the right position based on the currently focused view in the chain. - /// - /// Views that are focusable should override - /// - /// to ensure - /// the cursor is placed in a location that makes sense. Unix terminals do not have - /// a way of hiding the cursor, so it can be distracting to have the cursor left at - /// the last focused view. Views should make sure that they place the cursor - /// in a visually sensible place. - public virtual void PositionCursor () - { - if (!CanBeVisible (this) || !Enabled) { - return; - } - - // BUGBUG: v2 - This needs to support children of Frames too - - if (Focused == null && SuperView != null) { - SuperView.EnsureFocus (); - } else if (Focused?.Visible == true && Focused?.Enabled == true && Focused?.Frame.Width > 0 && Focused.Frame.Height > 0) { - Focused.PositionCursor (); - } else if (Focused?.Visible == true && Focused?.Enabled == false) { - Focused = null; - } else if (CanFocus && HasFocus && Visible && Frame.Width > 0 && Frame.Height > 0) { - Move (TextFormatter.HotKeyPos == -1 ? 0 : TextFormatter.CursorPosition, 0); - } else { - Move (_frame.X, _frame.Y); - } - } - #endregion Focus -} \ No newline at end of file +namespace Terminal.Gui; + +public partial class View +{ + private static readonly IList _empty = new List (0).AsReadOnly (); + + internal bool _addingView; + + private List _subviews; // This is null, and allocated on demand. + + private View _superView; + + /// + /// Indicates whether the view was added to . + /// + public bool IsAdded { get; private set; } + + /// + /// Returns a value indicating if this View is currently on Top (Active) + /// + public bool IsCurrentTop => Application.Current == this; + + /// + /// This returns a list of the subviews contained by this view. + /// + /// The subviews. + public IList Subviews => _subviews?.AsReadOnly () ?? _empty; + + /// + /// Returns the container for this view, or null if this view has not been added to a container. + /// + /// The super view. + public virtual View SuperView + { + get => _superView; + set => throw new NotImplementedException (); + } + + // Internally, we use InternalSubviews rather than subviews, as we do not expect us + // to make the same mistakes our users make when they poke at the Subviews. + internal IList InternalSubviews => _subviews ?? _empty; + + /// + /// Adds a subview (child) to this view. + /// + /// + /// The Views that have been added to this view can be retrieved via the property. + /// See also + /// + public virtual void Add (View view) + { + if (view == null) + { + return; + } + + if (_subviews == null) + { + _subviews = new List (); + } + + if (_tabIndexes == null) + { + _tabIndexes = new List (); + } + + _subviews.Add (view); + _tabIndexes.Add (view); + view._superView = this; + + if (view.CanFocus) + { + _addingView = true; + + if (SuperView?.CanFocus == false) + { + SuperView._addingView = true; + SuperView.CanFocus = true; + SuperView._addingView = false; + } + + CanFocus = true; + view._tabIndex = _tabIndexes.IndexOf (view); + _addingView = false; + } + + if (view.Enabled && !Enabled) + { + view._oldEnabled = true; + view.Enabled = false; + } + + OnAdded (new SuperViewChangedEventArgs (this, view)); + + if (IsInitialized && !view.IsInitialized) + { + view.BeginInit (); + view.EndInit (); + } + + CheckDimAuto (); + SetNeedsLayout (); + SetNeedsDisplay (); + } + + /// + /// Adds the specified views (children) to the view. + /// + /// Array of one or more views (can be optional parameter). + /// + /// The Views that have been added to this view can be retrieved via the property. + /// See also + /// + public void Add (params View [] views) + { + if (views == null) + { + return; + } + + foreach (View view in views) + { + Add (view); + } + } + + /// + /// Event fired when this view is added to another. + /// + public event EventHandler Added; + + /// + /// Moves the subview backwards in the hierarchy, only one step + /// + /// The subview to send backwards + /// + /// If you want to send the view all the way to the back use SendSubviewToBack. + /// + public void BringSubviewForward (View subview) + { + PerformActionForSubview ( + subview, + x => + { + int idx = _subviews.IndexOf (x); + + if (idx + 1 < _subviews.Count) + { + _subviews.Remove (x); + _subviews.Insert (idx + 1, x); + } + }); + } + + /// + /// Brings the specified subview to the front so it is drawn on top of any other views. + /// + /// The subview to send to the front + /// + /// . + /// + public void BringSubviewToFront (View subview) + { + PerformActionForSubview ( + subview, + x => + { + _subviews.Remove (x); + _subviews.Add (x); + }); + } + + /// + /// Get the top superview of a given . + /// + /// The superview view. + public View GetTopSuperView (View view = null, View superview = null) + { + View top = superview ?? Application.Top; + + for (View v = view?.SuperView ?? this?.SuperView; v != null; v = v.SuperView) + { + top = v; + + if (top == superview) + { + break; + } + } + + return top; + } + + /// + /// Method invoked when a subview is being added to this view. + /// + /// Event where is the subview being added. + public virtual void OnAdded (SuperViewChangedEventArgs e) + { + View view = e.Child; + view.IsAdded = true; + view.OnResizeNeeded (); + view.Added?.Invoke (this, e); + } + + /// + /// Method invoked when a subview is being removed from this view. + /// + /// Event args describing the subview being removed. + public virtual void OnRemoved (SuperViewChangedEventArgs e) + { + View view = e.Child; + view.IsAdded = false; + view.Removed?.Invoke (this, e); + } + + /// + /// Removes a subview added via or from this View. + /// + /// + /// + public virtual void Remove (View view) + { + if (view == null || _subviews == null) + { + return; + } + + Rect touched = view.Frame; + _subviews.Remove (view); + _tabIndexes.Remove (view); + view._superView = null; + view._tabIndex = -1; + SetNeedsLayout (); + SetNeedsDisplay (); + + foreach (View v in _subviews) + { + if (v.Frame.IntersectsWith (touched)) + { + view.SetNeedsDisplay (); + } + } + + OnRemoved (new SuperViewChangedEventArgs (this, view)); + + if (Focused == view) + { + Focused = null; + } + } + + /// + /// Removes all subviews (children) added via or from this View. + /// + public virtual void RemoveAll () + { + if (_subviews == null) + { + return; + } + + while (_subviews.Count > 0) + { + Remove (_subviews [0]); + } + } + + /// + /// Event fired when this view is removed from another. + /// + public event EventHandler Removed; + + /// + /// Moves the subview backwards in the hierarchy, only one step + /// + /// The subview to send backwards + /// + /// If you want to send the view all the way to the back use SendSubviewToBack. + /// + public void SendSubviewBackwards (View subview) + { + PerformActionForSubview ( + subview, + x => + { + int idx = _subviews.IndexOf (x); + + if (idx > 0) + { + _subviews.Remove (x); + _subviews.Insert (idx - 1, x); + } + }); + } + + /// + /// Sends the specified subview to the front so it is the first view drawn + /// + /// The subview to send to the front + /// + /// . + /// + public void SendSubviewToBack (View subview) + { + PerformActionForSubview ( + subview, + x => + { + _subviews.Remove (x); + _subviews.Insert (0, subview); + }); + } + + private void PerformActionForSubview (View subview, Action action) + { + if (_subviews.Contains (subview)) + { + action (subview); + } + + SetNeedsDisplay (); + subview.SetNeedsDisplay (); + } + + #region Focus + + internal enum Direction + { + Forward, + Backward + } + + /// + /// Event fired when the view gets focus. + /// + public event EventHandler Enter; + + /// + /// Event fired when the view looses focus. + /// + public event EventHandler Leave; + + private Direction _focusDirection; + + internal Direction FocusDirection + { + get => SuperView?.FocusDirection ?? _focusDirection; + set + { + if (SuperView != null) + { + SuperView.FocusDirection = value; + } + else + { + _focusDirection = value; + } + } + } + + // BUGBUG: v2 - Seems weird that this is in View and not Responder. + private bool _hasFocus; + + /// + public override bool HasFocus => _hasFocus; + + private void SetHasFocus (bool value, View view, bool force = false) + { + if (_hasFocus != value || force) + { + _hasFocus = value; + + if (value) + { + OnEnter (view); + } + else + { + OnLeave (view); + } + + SetNeedsDisplay (); + } + + // Remove focus down the chain of subviews if focus is removed + if (!value && Focused != null) + { + View f = Focused; + f.OnLeave (view); + f.SetHasFocus (false, view); + Focused = null; + } + } + + /// + /// Event fired when the value is being changed. + /// + public event EventHandler CanFocusChanged; + + /// + public override void OnCanFocusChanged () { CanFocusChanged?.Invoke (this, EventArgs.Empty); } + + private bool _oldCanFocus; + + /// + public override bool CanFocus + { + get => base.CanFocus; + set + { + if (!_addingView && IsInitialized && SuperView?.CanFocus == false && value) + { + throw new InvalidOperationException ("Cannot set CanFocus to true if the SuperView CanFocus is false!"); + } + + if (base.CanFocus != value) + { + base.CanFocus = value; + + switch (value) + { + case false when _tabIndex > -1: + TabIndex = -1; + + break; + case true when SuperView?.CanFocus == false && _addingView: + SuperView.CanFocus = true; + + break; + } + + if (value && _tabIndex == -1) + { + TabIndex = SuperView != null ? SuperView._tabIndexes.IndexOf (this) : -1; + } + + TabStop = value; + + if (!value && SuperView?.Focused == this) + { + SuperView.Focused = null; + } + + if (!value && HasFocus) + { + SetHasFocus (false, this); + SuperView?.EnsureFocus (); + + if (SuperView != null && SuperView.Focused == null) + { + SuperView.FocusNext (); + + if (SuperView.Focused == null && Application.Current != null) + { + Application.Current.FocusNext (); + } + + Application.BringOverlappedTopToFront (); + } + } + + if (_subviews != null && IsInitialized) + { + foreach (View view in _subviews) + { + if (view.CanFocus != value) + { + if (!value) + { + view._oldCanFocus = view.CanFocus; + view._oldTabIndex = view._tabIndex; + view.CanFocus = false; + view._tabIndex = -1; + } + else + { + if (_addingView) + { + view._addingView = true; + } + + view.CanFocus = view._oldCanFocus; + view._tabIndex = view._oldTabIndex; + view._addingView = false; + } + } + } + } + + OnCanFocusChanged (); + SetNeedsDisplay (); + } + } + } + + /// + public override bool OnEnter (View view) + { + var args = new FocusEventArgs (view); + Enter?.Invoke (this, args); + + if (args.Handled) + { + return true; + } + + if (base.OnEnter (view)) + { + return true; + } + + return false; + } + + /// + public override bool OnLeave (View view) + { + var args = new FocusEventArgs (view); + Leave?.Invoke (this, args); + + if (args.Handled) + { + return true; + } + + if (base.OnLeave (view)) + { + return true; + } + + Driver?.SetCursorVisibility (CursorVisibility.Invisible); + + return false; + } + + /// + /// Returns the currently focused view inside this view, or null if nothing is focused. + /// + /// The focused. + public View Focused { get; private set; } + + /// + /// Returns the most focused view in the chain of subviews (the leaf view that has the focus). + /// + /// The most focused View. + public View MostFocused + { + get + { + if (Focused == null) + { + return null; + } + + View most = Focused.MostFocused; + + if (most != null) + { + return most; + } + + return Focused; + } + } + + /// + /// Causes the specified subview to have focus. + /// + /// View. + private void SetFocus (View view) + { + if (view == null) + { + return; + } + + //Console.WriteLine ($"Request to focus {view}"); + if (!view.CanFocus || !view.Visible || !view.Enabled) + { + return; + } + + if (Focused?._hasFocus == true && Focused == view) + { + return; + } + + if ((Focused?._hasFocus == true && Focused?.SuperView == view) || view == this) + { + if (!view._hasFocus) + { + view._hasFocus = true; + } + + return; + } + + // Make sure that this view is a subview + View c; + + for (c = view._superView; c != null; c = c._superView) + { + if (c == this) + { + break; + } + } + + if (c == null) + { + throw new ArgumentException ("the specified view is not part of the hierarchy of this view"); + } + + if (Focused != null) + { + Focused.SetHasFocus (false, view); + } + + View f = Focused; + Focused = view; + Focused.SetHasFocus (true, f); + Focused.EnsureFocus (); + + // Send focus upwards + if (SuperView != null) + { + SuperView.SetFocus (this); + } + else + { + SetFocus (this); + } + } + + /// + /// Causes the specified view and the entire parent hierarchy to have the focused order updated. + /// + public void SetFocus () + { + if (!CanBeVisible (this) || !Enabled) + { + if (HasFocus) + { + SetHasFocus (false, this); + } + + return; + } + + if (SuperView != null) + { + SuperView.SetFocus (this); + } + else + { + SetFocus (this); + } + } + + /// + /// Finds the first view in the hierarchy that wants to get the focus if nothing is currently focused, otherwise, does + /// nothing. + /// + public void EnsureFocus () + { + if (Focused == null && _subviews?.Count > 0) + { + if (FocusDirection == Direction.Forward) + { + FocusFirst (); + } + else + { + FocusLast (); + } + } + } + + /// + /// Focuses the first focusable subview if one exists. + /// + public void FocusFirst () + { + if (!CanBeVisible (this)) + { + return; + } + + if (_tabIndexes == null) + { + SuperView?.SetFocus (this); + + return; + } + + foreach (View view in _tabIndexes) + { + if (view.CanFocus && view._tabStop && view.Visible && view.Enabled) + { + SetFocus (view); + + return; + } + } + } + + /// + /// Focuses the last focusable subview if one exists. + /// + public void FocusLast () + { + if (!CanBeVisible (this)) + { + return; + } + + if (_tabIndexes == null) + { + SuperView?.SetFocus (this); + + return; + } + + for (int i = _tabIndexes.Count; i > 0;) + { + i--; + + View v = _tabIndexes [i]; + + if (v.CanFocus && v._tabStop && v.Visible && v.Enabled) + { + SetFocus (v); + + return; + } + } + } + + /// + /// Focuses the previous view. + /// + /// if previous was focused, otherwise. + public bool FocusPrev () + { + if (!CanBeVisible (this)) + { + return false; + } + + FocusDirection = Direction.Backward; + + if (_tabIndexes == null || _tabIndexes.Count == 0) + { + return false; + } + + if (Focused == null) + { + FocusLast (); + + return Focused != null; + } + + int focusedIdx = -1; + + for (int i = _tabIndexes.Count; i > 0;) + { + i--; + View w = _tabIndexes [i]; + + if (w.HasFocus) + { + if (w.FocusPrev ()) + { + return true; + } + + focusedIdx = i; + + continue; + } + + if (w.CanFocus && focusedIdx != -1 && w._tabStop && w.Visible && w.Enabled) + { + Focused.SetHasFocus (false, w); + + if (w.CanFocus && w._tabStop && w.Visible && w.Enabled) + { + w.FocusLast (); + } + + SetFocus (w); + + return true; + } + } + + if (Focused != null) + { + Focused.SetHasFocus (false, this); + Focused = null; + } + + return false; + } + + /// + /// Focuses the next view. + /// + /// if next was focused, otherwise. + public bool FocusNext () + { + if (!CanBeVisible (this)) + { + return false; + } + + FocusDirection = Direction.Forward; + + if (_tabIndexes == null || _tabIndexes.Count == 0) + { + return false; + } + + if (Focused == null) + { + FocusFirst (); + + return Focused != null; + } + + int focusedIdx = -1; + + for (var i = 0; i < _tabIndexes.Count; i++) + { + View w = _tabIndexes [i]; + + if (w.HasFocus) + { + if (w.FocusNext ()) + { + return true; + } + + focusedIdx = i; + + continue; + } + + if (w.CanFocus && focusedIdx != -1 && w._tabStop && w.Visible && w.Enabled) + { + Focused.SetHasFocus (false, w); + + if (w.CanFocus && w._tabStop && w.Visible && w.Enabled) + { + w.FocusFirst (); + } + + SetFocus (w); + + return true; + } + } + + if (Focused != null) + { + Focused.SetHasFocus (false, this); + Focused = null; + } + + return false; + } + + private View GetMostFocused (View view) + { + if (view == null) + { + return null; + } + + return view.Focused != null ? GetMostFocused (view.Focused) : view; + } + + /// + /// Positions the cursor in the right position based on the currently focused view in the chain. + /// + /// Views that are focusable should override + /// + /// to ensure + /// the cursor is placed in a location that makes sense. Unix terminals do not have + /// a way of hiding the cursor, so it can be distracting to have the cursor left at + /// the last focused view. Views should make sure that they place the cursor + /// in a visually sensible place. + public virtual void PositionCursor () + { + if (!CanBeVisible (this) || !Enabled) + { + return; + } + + // BUGBUG: v2 - This needs to support children of Frames too + + if (Focused == null && SuperView != null) + { + SuperView.EnsureFocus (); + } + else if (Focused?.Visible == true && Focused?.Enabled == true && Focused?.Frame.Width > 0 && Focused.Frame.Height > 0) + { + Focused.PositionCursor (); + } + else if (Focused?.Visible == true && Focused?.Enabled == false) + { + Focused = null; + } + else if (CanFocus && HasFocus && Visible && Frame.Width > 0 && Frame.Height > 0) + { + Move (TextFormatter.HotKeyPos == -1 ? 0 : TextFormatter.CursorPosition, 0); + } + else + { + Move (_frame.X, _frame.Y); + } + } + + #endregion Focus +} diff --git a/Terminal.Gui/View/ViewText.cs b/Terminal.Gui/View/ViewText.cs index cd7fb74c4..c26832681 100644 --- a/Terminal.Gui/View/ViewText.cs +++ b/Terminal.Gui/View/ViewText.cs @@ -1,360 +1,424 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; +namespace Terminal.Gui; -namespace Terminal.Gui; +public partial class View +{ + private string _text; -public partial class View { - string _text; + /// + /// Gets or sets whether trailing spaces at the end of word-wrapped lines are preserved + /// or not when is enabled. + /// If trailing spaces at the end of wrapped lines will be removed when + /// is formatted for display. The default is . + /// + public virtual bool PreserveTrailingSpaces + { + get => TextFormatter.PreserveTrailingSpaces; + set + { + if (TextFormatter.PreserveTrailingSpaces != value) + { + TextFormatter.PreserveTrailingSpaces = value; + TextFormatter.NeedsFormat = true; + } + } + } - /// - /// The text displayed by the . - /// - /// - /// - /// The text will be drawn before any subviews are drawn. - /// - /// - /// The text will be drawn starting at the view origin (0, 0) and will be formatted according - /// to and . - /// - /// - /// The text will word-wrap to additional lines if it does not fit horizontally. If 's height - /// is 1, the text will be clipped. - /// - /// - /// Set the to enable hotkey support. To disable hotkey support set - /// to - /// (Rune)0xffff. - /// - /// - /// If is true, the will be adjusted to fit the text. - /// - /// - public virtual string Text { - get => _text; - set { - _text = value; - SetHotKey (); - UpdateTextFormatterText (); - OnResizeNeeded (); + /// + /// The text displayed by the . + /// + /// + /// + /// The text will be drawn before any subviews are drawn. + /// + /// + /// The text will be drawn starting at the view origin (0, 0) and will be formatted according + /// to and . + /// + /// + /// The text will word-wrap to additional lines if it does not fit horizontally. If 's height + /// is 1, the text will be clipped. + /// + /// + /// Set the to enable hotkey support. To disable hotkey support set + /// to + /// (Rune)0xffff. + /// + /// + /// If is true, the will be adjusted to fit the text. + /// + /// + public virtual string Text + { + get => _text; + set + { + _text = value; + SetHotKey (); + UpdateTextFormatterText (); + OnResizeNeeded (); #if DEBUG - if (_text != null && string.IsNullOrEmpty (Id)) { - Id = _text; - } + if (_text != null && string.IsNullOrEmpty (Id)) + { + Id = _text; + } #endif - } - } + } + } - /// - /// Gets or sets the used to format . - /// - public TextFormatter TextFormatter { get; set; } + /// + /// Gets or sets how the View's is aligned horizontally when drawn. Changing this property will + /// redisplay the . + /// + /// + /// + /// If is true, the will be adjusted to fit the text. + /// + /// + /// The text alignment. + public virtual TextAlignment TextAlignment + { + get => TextFormatter.Alignment; + set + { + TextFormatter.Alignment = value; + UpdateTextFormatterText (); + OnResizeNeeded (); + } + } - /// - /// Gets or sets whether trailing spaces at the end of word-wrapped lines are preserved - /// or not when is enabled. - /// If trailing spaces at the end of wrapped lines will be removed when - /// is formatted for display. The default is . - /// - public virtual bool PreserveTrailingSpaces { - get => TextFormatter.PreserveTrailingSpaces; - set { - if (TextFormatter.PreserveTrailingSpaces != value) { - TextFormatter.PreserveTrailingSpaces = value; - TextFormatter.NeedsFormat = true; - } - } - } + /// + /// Gets or sets the direction of the View's . Changing this property will redisplay the + /// . + /// + /// + /// + /// If is true, the will be adjusted to fit the text. + /// + /// + /// The text alignment. + public virtual TextDirection TextDirection + { + get => TextFormatter.Direction; + set + { + UpdateTextDirection (value); + TextFormatter.Direction = value; + } + } - /// - /// Gets or sets how the View's is aligned horizontally when drawn. Changing this property will - /// redisplay the . - /// - /// - /// - /// If is true, the will be adjusted to fit the text. - /// - /// - /// The text alignment. - public virtual TextAlignment TextAlignment { - get => TextFormatter.Alignment; - set { - TextFormatter.Alignment = value; - UpdateTextFormatterText (); - OnResizeNeeded (); - } - } + /// + /// Gets or sets the used to format . + /// + public TextFormatter TextFormatter { get; set; } - /// - /// Gets or sets how the View's is aligned vertically when drawn. Changing this property will redisplay - /// the . - /// - /// - /// - /// If is true, the will be adjusted to fit the text. - /// - /// - /// The text alignment. - public virtual VerticalTextAlignment VerticalTextAlignment { - get => TextFormatter.VerticalAlignment; - set { - TextFormatter.VerticalAlignment = value; - SetNeedsDisplay (); - } - } + /// + /// Gets or sets how the View's is aligned vertically when drawn. Changing this property will + /// redisplay + /// the . + /// + /// + /// + /// If is true, the will be adjusted to fit the text. + /// + /// + /// The text alignment. + public virtual VerticalTextAlignment VerticalTextAlignment + { + get => TextFormatter.VerticalAlignment; + set + { + TextFormatter.VerticalAlignment = value; + SetNeedsDisplay (); + } + } - /// - /// Gets or sets the direction of the View's . Changing this property will redisplay the - /// . - /// - /// - /// - /// If is true, the will be adjusted to fit the text. - /// - /// - /// The text alignment. - public virtual TextDirection TextDirection { - get => TextFormatter.Direction; - set { - UpdateTextDirection (value); - TextFormatter.Direction = value; - } - } + /// + /// Gets the width or height of the characters + /// in the property. + /// + /// + /// Only the first HotKey specifier found in is supported. + /// + /// + /// If (the default) the width required for the HotKey specifier is returned. Otherwise the + /// height + /// is returned. + /// + /// + /// The number of characters required for the . If the text + /// direction specified + /// by does not match the parameter, 0 is returned. + /// + public int GetHotKeySpecifierLength (bool isWidth = true) + { + if (isWidth) + { + return TextFormatter.IsHorizontalDirection (TextDirection) && TextFormatter.Text?.Contains ((char)HotKeySpecifier.Value) == true + ? Math.Max (HotKeySpecifier.GetColumns (), 0) + : 0; + } - /// - /// Can be overridden if the has - /// different format than the default. - /// - protected virtual void UpdateTextFormatterText () - { - if (TextFormatter != null) { - TextFormatter.Text = _text; - } - } + return TextFormatter.IsVerticalDirection (TextDirection) && TextFormatter.Text?.Contains ((char)HotKeySpecifier.Value) == true + ? Math.Max (HotKeySpecifier.GetColumns (), 0) + : 0; + } - void UpdateTextDirection (TextDirection newDirection) - { - var directionChanged = TextFormatter.IsHorizontalDirection (TextFormatter.Direction) != TextFormatter.IsHorizontalDirection (newDirection); - TextFormatter.Direction = newDirection; + // TODO: Refactor this to return the Bounds size, not Frame. Move code that accounts for + // TODO: Thickness out to callers. + /// + /// Gets the size of the required to fit within using the + /// text + /// formatting settings of and accounting for . + /// + /// + /// + /// The of the required to fit the formatted text. + public Size GetTextAutoSize () + { + var x = 0; + var y = 0; - var isValidOldAutoSize = AutoSize && IsValidAutoSize (out var _); + if (IsInitialized) + { + x = Bounds.X; + y = Bounds.Y; + } - UpdateTextFormatterText (); + Rect rect = TextFormatter.CalcRect (x, y, TextFormatter.Text, TextFormatter.Direction); - if (!ValidatePosDim && directionChanged && AutoSize || ValidatePosDim && directionChanged && AutoSize && isValidOldAutoSize) { - OnResizeNeeded (); - } else if (directionChanged && IsAdded) { - ResizeBoundsToFit (Bounds.Size); - // BUGBUG: I think this call is redundant. - SetFrameToFitText (); - } else { - SetFrameToFitText (); - } - SetTextFormatterSize (); - SetNeedsDisplay (); - } + int newWidth = rect.Size.Width + - GetHotKeySpecifierLength () + + (Margin == null ? 0 : Margin.Thickness.Horizontal + Border.Thickness.Horizontal + Padding.Thickness.Horizontal); + int newHeight = rect.Size.Height + - GetHotKeySpecifierLength (false) + + (Margin == null ? 0 : Margin.Thickness.Vertical + Border.Thickness.Vertical + Padding.Thickness.Vertical); - /// - /// Sets the size of the View to the minimum width or height required to fit . - /// - /// - /// if the size was changed; if == - /// or - /// will not fit. - /// - /// - /// Always returns if is or - /// if (Horizontal) or (Vertical) are not not set or zero. - /// Does not take into account word wrapping. - /// - bool SetFrameToFitText () - { - // BUGBUG: This API is broken - should not assume Frame.Height == Bounds.Height - // - // Gets the minimum dimensions required to fit the View's , factoring in . - // - // The minimum dimensions required. - // if the dimensions fit within the View's , otherwise. - // - // Always returns if is or - // if (Horizontal) or (Vertical) are not not set or zero. - // Does not take into account word wrapping. - // - bool GetMinimumSizeOfText (out Size sizeRequired) - { - if (!IsInitialized) { - sizeRequired = new Size (0, 0); - return false; - } - sizeRequired = Bounds.Size; + return new Size (newWidth, newHeight); + } - if (AutoSize || string.IsNullOrEmpty (TextFormatter.Text)) { - return false; - } + /// + /// Can be overridden if the has + /// different format than the default. + /// + protected virtual void UpdateTextFormatterText () + { + if (TextFormatter != null) + { + TextFormatter.Text = _text; + } + } - switch (TextFormatter.IsVerticalDirection (TextDirection)) { - case true: - var colWidth = TextFormatter.GetSumMaxCharWidth (new List { TextFormatter.Text }, 0, 1); - // TODO: v2 - This uses frame.Width; it should only use Bounds - if (_frame.Width < colWidth && - (Width == null || - Bounds.Width >= 0 && - Width is Dim.DimAbsolute && - Width.Anchor (0) >= 0 && - Width.Anchor (0) < colWidth)) { - sizeRequired = new Size (colWidth, Bounds.Height); - return true; - } - break; - default: - if (_frame.Height < 1 && - (Height == null || - Height is Dim.DimAbsolute && - Height.Anchor (0) == 0)) { - sizeRequired = new Size (Bounds.Width, 1); - return true; - } - break; - } - return false; - } + /// + /// Gets the dimensions required for ignoring a . + /// + /// + internal Size GetSizeNeededForTextWithoutHotKey () + { + return new Size ( + TextFormatter.Size.Width - GetHotKeySpecifierLength (), + TextFormatter.Size.Height - GetHotKeySpecifierLength (false)); + } - if (GetMinimumSizeOfText (out var size)) { - // TODO: This is a hack. - //_width = size.Width; - //_height = size.Height; - _frame = new Rect (_frame.Location, size); - //throw new InvalidOperationException ("This is a hack."); - return true; - } - return false; - } + /// + /// Sets .Size to the current size, adjusted for + /// . + /// + /// + /// Use this API to set when the view has changed such that the + /// size required to fit the text has changed. + /// changes. + /// + /// + internal void SetTextFormatterSize () + { + if (!IsInitialized) + { + TextFormatter.Size = Size.Empty; - /// - /// Gets the width or height of the characters - /// in the property. - /// - /// - /// Only the first HotKey specifier found in is supported. - /// - /// - /// If (the default) the width required for the HotKey specifier is returned. Otherwise the height - /// is returned. - /// - /// - /// The number of characters required for the . If the text - /// direction specified - /// by does not match the parameter, 0 is returned. - /// - public int GetHotKeySpecifierLength (bool isWidth = true) - { - if (isWidth) { - return TextFormatter.IsHorizontalDirection (TextDirection) && - TextFormatter.Text?.Contains ((char)HotKeySpecifier.Value) == true - ? Math.Max (HotKeySpecifier.GetColumns (), 0) : 0; - } - return TextFormatter.IsVerticalDirection (TextDirection) && - TextFormatter.Text?.Contains ((char)HotKeySpecifier.Value) == true - ? Math.Max (HotKeySpecifier.GetColumns (), 0) : 0; - } + return; + } - /// - /// Gets the dimensions required for ignoring a . - /// - /// - internal Size GetSizeNeededForTextWithoutHotKey () => new (TextFormatter.Size.Width - GetHotKeySpecifierLength (), - TextFormatter.Size.Height - GetHotKeySpecifierLength (false)); + if (string.IsNullOrEmpty (TextFormatter.Text)) + { + TextFormatter.Size = Bounds.Size; - /// - /// Sets .Size to the current size, adjusted for - /// . - /// - /// - /// Use this API to set when the view has changed such that the - /// size required to fit the text has changed. - /// changes. - /// - /// - internal void SetTextFormatterSize () - { - if (!IsInitialized) { - TextFormatter.Size = Size.Empty; - return; - } + return; + } - if (string.IsNullOrEmpty (TextFormatter.Text)) { - TextFormatter.Size = Bounds.Size; - return; - } + int w = Bounds.Size.Width + GetHotKeySpecifierLength (); - var w = Bounds.Size.Width + GetHotKeySpecifierLength (); - if (Width is Dim.DimAuto widthAuto && widthAuto._style != Dim.DimAutoStyle.Subviews) { - if (Height is Dim.DimAuto) { - // Both are auto. - TextFormatter.Size = new Size (SuperView?.Bounds.Width ?? 0, SuperView?.Bounds.Height ?? 0); - } else { - TextFormatter.Size = new Size (SuperView?.Bounds.Width ?? 0, Bounds.Size.Height + GetHotKeySpecifierLength ()); - } - w = TextFormatter.GetFormattedSize ().Width; - } else { - TextFormatter.Size = new Size (w, SuperView?.Bounds.Height ?? 0); - } + if (Width is Dim.DimAuto widthAuto && widthAuto._style != Dim.DimAutoStyle.Subviews) + { + if (Height is Dim.DimAuto) + { + // Both are auto. + TextFormatter.Size = new Size (SuperView?.Bounds.Width ?? 0, SuperView?.Bounds.Height ?? 0); + } + else + { + TextFormatter.Size = new Size (SuperView?.Bounds.Width ?? 0, Bounds.Size.Height + GetHotKeySpecifierLength ()); + } - var h = Bounds.Size.Height + GetHotKeySpecifierLength (); - if (Height is Dim.DimAuto heightAuto && heightAuto._style != Dim.DimAutoStyle.Subviews) { - TextFormatter.NeedsFormat = true; - h = TextFormatter.GetFormattedSize ().Height; - } - TextFormatter.Size = new Size (w, h); - } + w = TextFormatter.GetFormattedSize ().Width; + } + else + { + TextFormatter.Size = new Size (w, SuperView?.Bounds.Height ?? 0); + } - // TODO: Refactor this to return the Bounds size, not Frame. Move code that accounts for - // TODO: Thickness out to callers. - /// - /// Gets the size of the required to fit within using the text - /// formatting settings of and accounting for . - /// - /// - /// - /// The of the required to fit the formatted text. - public Size GetTextAutoSize () - { - var x = 0; - var y = 0; - if (IsInitialized) { - x = Bounds.X; - y = Bounds.Y; - } - var rect = TextFormatter.CalcRect (x, y, TextFormatter.Text, TextFormatter.Direction); - int newWidth = rect.Size.Width - GetHotKeySpecifierLength () + (Margin == null ? 0 : Margin.Thickness.Horizontal + Border.Thickness.Horizontal + Padding.Thickness.Horizontal); - int newHeight = rect.Size.Height - GetHotKeySpecifierLength (false) + (Margin == null ? 0 : Margin.Thickness.Vertical + Border.Thickness.Vertical + Padding.Thickness.Vertical); - return new Size (newWidth, newHeight); - } + int h = Bounds.Size.Height + GetHotKeySpecifierLength (); - bool IsValidAutoSize (out Size autoSize) - { - var rect = TextFormatter.CalcRect (_frame.X, _frame.Y, TextFormatter.Text, TextDirection); - autoSize = new Size (rect.Size.Width - GetHotKeySpecifierLength (), - rect.Size.Height - GetHotKeySpecifierLength (false)); - return !(ValidatePosDim && (!(Width is Dim.DimAbsolute) || !(Height is Dim.DimAbsolute)) || - _frame.Size.Width != rect.Size.Width - GetHotKeySpecifierLength () || - _frame.Size.Height != rect.Size.Height - GetHotKeySpecifierLength (false)); - } + if (Height is Dim.DimAuto heightAuto && heightAuto._style != Dim.DimAutoStyle.Subviews) + { + TextFormatter.NeedsFormat = true; + h = TextFormatter.GetFormattedSize ().Height; + } - bool IsValidAutoSizeWidth (Dim width) - { - var rect = TextFormatter.CalcRect (_frame.X, _frame.Y, TextFormatter.Text, TextDirection); - var dimValue = width.Anchor (0); - return !(ValidatePosDim && !(width is Dim.DimAbsolute) || dimValue != rect.Size.Width - GetHotKeySpecifierLength ()); - } + TextFormatter.Size = new Size (w, h); + } - bool IsValidAutoSizeHeight (Dim height) - { - var rect = TextFormatter.CalcRect (_frame.X, _frame.Y, TextFormatter.Text, TextDirection); - var dimValue = height.Anchor (0); - return !(ValidatePosDim && !(height is Dim.DimAbsolute) || dimValue != rect.Size.Height - GetHotKeySpecifierLength (false)); - } -} \ No newline at end of file + private bool IsValidAutoSize (out Size autoSize) + { + Rect rect = TextFormatter.CalcRect (_frame.X, _frame.Y, TextFormatter.Text, TextDirection); + + autoSize = new Size ( + rect.Size.Width - GetHotKeySpecifierLength (), + rect.Size.Height - GetHotKeySpecifierLength (false)); + + return !((ValidatePosDim && (!(Width is Dim.DimAbsolute) || !(Height is Dim.DimAbsolute))) + || _frame.Size.Width != rect.Size.Width - GetHotKeySpecifierLength () + || _frame.Size.Height != rect.Size.Height - GetHotKeySpecifierLength (false)); + } + + private bool IsValidAutoSizeHeight (Dim height) + { + Rect rect = TextFormatter.CalcRect (_frame.X, _frame.Y, TextFormatter.Text, TextDirection); + int dimValue = height.Anchor (0); + + return !((ValidatePosDim && !(height is Dim.DimAbsolute)) || dimValue != rect.Size.Height - GetHotKeySpecifierLength (false)); + } + + private bool IsValidAutoSizeWidth (Dim width) + { + Rect rect = TextFormatter.CalcRect (_frame.X, _frame.Y, TextFormatter.Text, TextDirection); + int dimValue = width.Anchor (0); + + return !((ValidatePosDim && !(width is Dim.DimAbsolute)) || dimValue != rect.Size.Width - GetHotKeySpecifierLength ()); + } + + /// + /// Sets the size of the View to the minimum width or height required to fit . + /// + /// + /// if the size was changed; if == + /// or + /// will not fit. + /// + /// + /// Always returns if is or + /// if (Horizontal) or (Vertical) are not not set or zero. + /// Does not take into account word wrapping. + /// + private bool SetFrameToFitText () + { + // BUGBUG: This API is broken - should not assume Frame.Height == Bounds.Height + // + // Gets the minimum dimensions required to fit the View's , factoring in . + // + // The minimum dimensions required. + // if the dimensions fit within the View's , otherwise. + // + // Always returns if is or + // if (Horizontal) or (Vertical) are not not set or zero. + // Does not take into account word wrapping. + // + bool GetMinimumSizeOfText (out Size sizeRequired) + { + if (!IsInitialized) + { + sizeRequired = new Size (0, 0); + + return false; + } + + sizeRequired = Bounds.Size; + + if (AutoSize || string.IsNullOrEmpty (TextFormatter.Text)) + { + return false; + } + + switch (TextFormatter.IsVerticalDirection (TextDirection)) + { + case true: + int colWidth = TextFormatter.GetSumMaxCharWidth (new List { TextFormatter.Text }, 0, 1); + + // TODO: v2 - This uses frame.Width; it should only use Bounds + if (_frame.Width < colWidth + && (Width == null || (Bounds.Width >= 0 && Width is Dim.DimAbsolute && Width.Anchor (0) >= 0 && Width.Anchor (0) < colWidth))) + { + sizeRequired = new Size (colWidth, Bounds.Height); + + return true; + } + + break; + default: + if (_frame.Height < 1 && (Height == null || (Height is Dim.DimAbsolute && Height.Anchor (0) == 0))) + { + sizeRequired = new Size (Bounds.Width, 1); + + return true; + } + + break; + } + + return false; + } + + if (GetMinimumSizeOfText (out Size size)) + { + // TODO: This is a hack. + //_width = size.Width; + //_height = size.Height; + _frame = new Rect (_frame.Location, size); + + //throw new InvalidOperationException ("This is a hack."); + return true; + } + + return false; + } + + private void UpdateTextDirection (TextDirection newDirection) + { + bool directionChanged = TextFormatter.IsHorizontalDirection (TextFormatter.Direction) != TextFormatter.IsHorizontalDirection (newDirection); + TextFormatter.Direction = newDirection; + + bool isValidOldAutoSize = AutoSize && IsValidAutoSize (out Size _); + + UpdateTextFormatterText (); + + if ((!ValidatePosDim && directionChanged && AutoSize) || (ValidatePosDim && directionChanged && AutoSize && isValidOldAutoSize)) + { + OnResizeNeeded (); + } + else if (directionChanged && IsAdded) + { + ResizeBoundsToFit (Bounds.Size); + + // BUGBUG: I think this call is redundant. + SetFrameToFitText (); + } + else + { + SetFrameToFitText (); + } + + SetTextFormatterSize (); + SetNeedsDisplay (); + } +} diff --git a/Terminal.Gui/Views/Toplevel.cs b/Terminal.Gui/Views/Toplevel.cs index 5dc62761d..1a110fa40 100644 --- a/Terminal.Gui/Views/Toplevel.cs +++ b/Terminal.Gui/Views/Toplevel.cs @@ -1,313 +1,1135 @@ -using System; -using System.Collections.Generic; -using System.Linq; - namespace Terminal.Gui; /// -/// Toplevel views are used for both an application's main view (filling the entire screen and -/// for modal (pop-up) views such as , , and -/// ). +/// Toplevel views are used for both an application's main view (filling the entire screen and +/// for modal (pop-up) views such as , , and +/// ). /// /// -/// +/// /// Toplevels can run as modal (popup) views, started by calling /// . /// They return control to the caller when has /// been called (which sets the property to false). -/// -/// +/// +/// /// A Toplevel is created when an application initializes Terminal.Gui by calling /// . /// The application Toplevel can be accessed via . Additional /// Toplevels can be created /// and run (e.g. s. To run a Toplevel, create the and /// call . -/// +/// /// -public partial class Toplevel : View { +public partial class Toplevel : View +{ + internal static Point? _dragPosition; + private Point _startGrabPoint; - /// - /// Initializes a new instance of the class with the specified - /// layout. - /// - /// - /// A Superview-relative rectangle specifying the location and size for the new - /// Toplevel - /// - public Toplevel (Rect frame) : base (frame) => SetInitialProperties (); + /// + /// Initializes a new instance of the class with the specified + /// layout. + /// + /// + /// A Superview-relative rectangle specifying the location and size for the new + /// Toplevel + /// + public Toplevel (Rect frame) : base (frame) { SetInitialProperties (); } - /// - /// Initializes a new instance of the class with - /// layout, defaulting to full screen. The and - /// properties - /// will be set to the dimensions of the terminal using . - /// - public Toplevel () - { - SetInitialProperties (); - Width = Dim.Fill (); - Height = Dim.Fill (); - } + /// + /// Initializes a new instance of the class with + /// layout, defaulting to full screen. The and + /// properties + /// will be set to the dimensions of the terminal using . + /// + public Toplevel () + { + SetInitialProperties (); + Width = Dim.Fill (); + Height = Dim.Fill (); + } - /// - /// Gets or sets whether the main loop for this is running or not. - /// - /// - /// Setting this property directly is discouraged. Use - /// instead. - /// - public bool Running { get; set; } + /// + /// Gets or sets a value indicating whether this can focus. + /// + /// true if can focus; otherwise, false. + public override bool CanFocus => SuperView == null ? true : base.CanFocus; - /// - /// Gets or sets a value indicating whether this can focus. - /// - /// true if can focus; otherwise, false. - public override bool CanFocus => SuperView == null ? true : base.CanFocus; + /// + /// if was already loaded by the + /// , otherwise. + /// + public bool IsLoaded { get; private set; } - /// - /// Determines whether the is modal or not. - /// If set to false (the default): - /// - /// - /// events will propagate keys upwards. - /// - /// - /// The Toplevel will act as an embedded view (not a modal/pop-up). - /// - /// - /// If set to true: - /// - /// - /// events will NOT propagate keys upwards. - /// - /// - /// - /// The Toplevel will and look like a modal (pop-up) (e.g. see - /// . - /// - /// - /// - /// - public bool Modal { get; set; } + /// + /// Gets or sets the menu for this Toplevel. + /// + public virtual MenuBar MenuBar { get; set; } - /// - /// Gets or sets the menu for this Toplevel. - /// - public virtual MenuBar MenuBar { get; set; } + /// + /// Determines whether the is modal or not. + /// If set to false (the default): + /// + /// + /// events will propagate keys upwards. + /// + /// + /// The Toplevel will act as an embedded view (not a modal/pop-up). + /// + /// + /// If set to true: + /// + /// + /// events will NOT propagate keys upwards. + /// + /// + /// + /// The Toplevel will and look like a modal (pop-up) (e.g. see + /// . + /// + /// + /// + /// + public bool Modal { get; set; } - /// - /// Gets or sets the status bar for this Toplevel. - /// - public virtual StatusBar StatusBar { get; set; } + /// + /// Gets or sets whether the main loop for this is running or not. + /// + /// + /// Setting this property directly is discouraged. Use + /// instead. + /// + public bool Running { get; set; } - /// - /// if was already loaded by the - /// , otherwise. - /// - public bool IsLoaded { get; private set; } + /// + /// Gets or sets the status bar for this Toplevel. + /// + public virtual StatusBar StatusBar { get; set; } - /// - /// Invoked when the has begun to be loaded. - /// A Loaded event handler is a good place to finalize initialization before calling - /// . - /// - public event EventHandler Loaded; + /// + /// Invoked when the Toplevel becomes the + /// Toplevel. + /// + public event EventHandler Activate; - /// - /// Invoked when the main loop has started it's first iteration. - /// Subscribe to this event to perform tasks when the has been laid out and - /// focus has been set. - /// changes. - /// - /// A Ready event handler is a good place to finalize initialization after calling - /// on this . - /// - /// - public event EventHandler Ready; + /// + public override void Add (View view) + { + CanFocus = true; + AddMenuStatusBar (view); + base.Add (view); + } - /// - /// Invoked when the Toplevel has been unloaded. - /// A Unloaded event handler is a good place to dispose objects after calling - /// . - /// - public event EventHandler Unloaded; + /// + /// Invoked when the last child of the Toplevel is closed from + /// by . + /// + public event EventHandler AllChildClosed; - /// - /// Invoked when the Toplevel becomes the - /// Toplevel. - /// - public event EventHandler Activate; + /// + /// Invoked when the is changed. + /// + public event EventHandler AlternateBackwardKeyChanged; - /// - /// Invoked when the Toplevel ceases to be the - /// Toplevel. - /// - public event EventHandler Deactivate; + /// + /// Invoked when the is changed. + /// + public event EventHandler AlternateForwardKeyChanged; - /// - /// Invoked when a child of the Toplevel is closed by - /// . - /// - public event EventHandler ChildClosed; + /// + /// Invoked when a child of the Toplevel is closed by + /// . + /// + public event EventHandler ChildClosed; - /// - /// Invoked when the last child of the Toplevel is closed from - /// by . - /// - public event EventHandler AllChildClosed; + /// + /// Invoked when a child Toplevel's has been loaded. + /// + public event EventHandler ChildLoaded; - /// - /// Invoked when the Toplevel's is being closed by - /// . - /// - public event EventHandler Closing; + /// + /// Invoked when a cjhild Toplevel's has been unloaded. + /// + public event EventHandler ChildUnloaded; - /// - /// Invoked when the Toplevel's is closed by - /// . - /// - public event EventHandler Closed; + /// + /// Invoked when the Toplevel's is closed by + /// . + /// + public event EventHandler Closed; - /// - /// Invoked when a child Toplevel's has been loaded. - /// - public event EventHandler ChildLoaded; + /// + /// Invoked when the Toplevel's is being closed by + /// . + /// + public event EventHandler Closing; - /// - /// Invoked when a cjhild Toplevel's has been unloaded. - /// - public event EventHandler ChildUnloaded; + /// + /// Invoked when the Toplevel ceases to be the + /// Toplevel. + /// + public event EventHandler Deactivate; - /// - /// Invoked when the terminal has been resized. The new of the terminal is provided. - /// - public event EventHandler SizeChanging; + /// + /// Invoked when the has begun to be loaded. + /// A Loaded event handler is a good place to finalize initialization before calling + /// . + /// + public event EventHandler Loaded; - // TODO: Make cancelable? - internal virtual void OnSizeChanging (SizeChangedEventArgs size) => SizeChanging?.Invoke (this, size); + /// + public override bool MouseEvent (MouseEvent mouseEvent) + { + if (!CanFocus) + { + return true; + } - internal virtual void OnChildUnloaded (Toplevel top) => ChildUnloaded?.Invoke (this, new ToplevelEventArgs (top)); + //System.Diagnostics.Debug.WriteLine ($"dragPosition before: {dragPosition.HasValue}"); - internal virtual void OnChildLoaded (Toplevel top) => ChildLoaded?.Invoke (this, new ToplevelEventArgs (top)); + int nx, ny; - internal virtual void OnClosed (Toplevel top) => Closed?.Invoke (this, new ToplevelEventArgs (top)); + if (!_dragPosition.HasValue + && (mouseEvent.Flags == MouseFlags.Button1Pressed + || mouseEvent.Flags == MouseFlags.Button2Pressed + || mouseEvent.Flags == MouseFlags.Button3Pressed)) + { + SetFocus (); + Application.BringOverlappedTopToFront (); - internal virtual bool OnClosing (ToplevelClosingEventArgs ev) - { - Closing?.Invoke (this, ev); - return ev.Cancel; - } + // Only start grabbing if the user clicks on the title bar. + // BUGBUG: Assumes Frame == Border and Title is always at Y == 0 + if (mouseEvent.Y == 0 && mouseEvent.Flags == MouseFlags.Button1Pressed) + { + _startGrabPoint = new Point (mouseEvent.X, mouseEvent.Y); + _dragPosition = new Point (); + nx = mouseEvent.X - mouseEvent.OfX; + ny = mouseEvent.Y - mouseEvent.OfY; + _dragPosition = new Point (nx, ny); + Application.GrabMouse (this); + } - internal virtual void OnAllChildClosed () => AllChildClosed?.Invoke (this, EventArgs.Empty); + //System.Diagnostics.Debug.WriteLine ($"Starting at {dragPosition}"); + return true; + } - internal virtual void OnChildClosed (Toplevel top) - { - if (IsOverlappedContainer) { - SetSubViewNeedsDisplay (); - } - ChildClosed?.Invoke (this, new ToplevelEventArgs (top)); - } + if (mouseEvent.Flags == (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition) || mouseEvent.Flags == MouseFlags.Button3Pressed) + { + if (_dragPosition.HasValue) + { + if (SuperView == null) + { + // Redraw the entire app window using just our Frame. Since we are + // Application.Top, and our Frame always == our Bounds (Location is always (0,0)) + // our Frame is actually view-relative (which is what Redraw takes). + // We need to pass all the view bounds because since the windows was + // moved around, we don't know exactly what was the affected region. + Application.Top.SetNeedsDisplay (); + } + else + { + SuperView.SetNeedsDisplay (); + } - internal virtual void OnDeactivate (Toplevel activated) => Deactivate?.Invoke (this, new ToplevelEventArgs (activated)); + // BUGBUG: Assumes Frame == Border? + GetLocationThatFits ( + this, + mouseEvent.X + (SuperView == null ? mouseEvent.OfX - _startGrabPoint.X : Frame.X - _startGrabPoint.X), + mouseEvent.Y + (SuperView == null ? mouseEvent.OfY - _startGrabPoint.Y : Frame.Y - _startGrabPoint.Y), + out nx, + out ny, + out _, + out _); - internal virtual void OnActivate (Toplevel deactivated) => Activate?.Invoke (this, new ToplevelEventArgs (deactivated)); + _dragPosition = new Point (nx, ny); + X = nx; + Y = ny; - /// - /// Called from before the redraws for - /// the first time. - /// - public virtual void OnLoaded () - { - IsLoaded = true; - foreach (Toplevel tl in Subviews.Where (v => v is Toplevel)) { - tl.OnLoaded (); - } - Loaded?.Invoke (this, EventArgs.Empty); - } + //System.Diagnostics.Debug.WriteLine ($"Drag: nx:{nx},ny:{ny}"); - /// - /// Called from after the has entered the - /// first iteration of the loop. - /// - internal virtual void OnReady () - { - foreach (Toplevel tl in Subviews.Where (v => v is Toplevel)) { - tl.OnReady (); - } - Ready?.Invoke (this, EventArgs.Empty); - } + SetNeedsDisplay (); - /// - /// Called from before the is disposed. - /// - internal virtual void OnUnloaded () - { - foreach (Toplevel tl in Subviews.Where (v => v is Toplevel)) { - tl.OnUnloaded (); - } - Unloaded?.Invoke (this, EventArgs.Empty); - } + return true; + } + } - void SetInitialProperties () - { - ColorScheme = Colors.ColorSchemes ["TopLevel"]; + if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Released) && _dragPosition.HasValue) + { + _dragPosition = null; + Application.UngrabMouse (); + } - Application.GrabbingMouse += Application_GrabbingMouse; - Application.UnGrabbingMouse += Application_UnGrabbingMouse; + //System.Diagnostics.Debug.WriteLine ($"dragPosition after: {dragPosition.HasValue}"); + //System.Diagnostics.Debug.WriteLine ($"Toplevel: {mouseEvent}"); + return false; + } - // TODO: v2 - ALL Views (Responders??!?!) should support the commands related to - // - Focus - // Move the appropriate AddCommand calls to `Responder` + /// + /// Virtual method to invoke the event. + /// + /// + public virtual void OnAlternateBackwardKeyChanged (KeyChangedEventArgs e) + { + KeyBindings.Replace (e.OldKey, e.NewKey); + AlternateBackwardKeyChanged?.Invoke (this, e); + } - // Things this view knows how to do - AddCommand (Command.QuitToplevel, () => { - QuitToplevel (); - return true; - }); - AddCommand (Command.Suspend, () => { - Driver.Suspend (); - ; - return true; - }); - AddCommand (Command.NextView, () => { - MoveNextView (); - return true; - }); - AddCommand (Command.PreviousView, () => { - MovePreviousView (); - return true; - }); - AddCommand (Command.NextViewOrTop, () => { - MoveNextViewOrTop (); - return true; - }); - AddCommand (Command.PreviousViewOrTop, () => { - MovePreviousViewOrTop (); - return true; - }); - AddCommand (Command.Refresh, () => { - Application.Refresh (); - return true; - }); + /// + /// Virtual method to invoke the event. + /// + /// + public virtual void OnAlternateForwardKeyChanged (KeyChangedEventArgs e) + { + KeyBindings.Replace (e.OldKey, e.NewKey); + AlternateForwardKeyChanged?.Invoke (this, e); + } + /// + public override void OnDrawContent (Rect contentArea) + { + if (!Visible) + { + return; + } - // Default keybindings for this view - KeyBindings.Add (Application.QuitKey, Command.QuitToplevel); + if (NeedsDisplay || SubViewNeedsDisplay || LayoutNeeded) + { + //Driver.SetAttribute (GetNormalColor ()); + // TODO: It's bad practice for views to always clear. Defeats the purpose of clipping etc... + Clear (); + LayoutSubviews (); + PositionToplevels (); - KeyBindings.Add (Key.CursorRight, Command.NextView); - KeyBindings.Add (Key.CursorDown, Command.NextView); - KeyBindings.Add (Key.CursorLeft, Command.PreviousView); - KeyBindings.Add (Key.CursorUp, Command.PreviousView); + if (this == Application.OverlappedTop) + { + foreach (Toplevel top in Application.OverlappedChildren.AsEnumerable ().Reverse ()) + { + if (top.Frame.IntersectsWith (Bounds)) + { + if (top != this && !top.IsCurrentTop && !OutsideTopFrame (top) && top.Visible) + { + top.SetNeedsLayout (); + top.SetNeedsDisplay (top.Bounds); + top.Draw (); + top.OnRenderLineCanvas (); + } + } + } + } - KeyBindings.Add (Key.Tab, Command.NextView); - KeyBindings.Add (Key.Tab.WithShift, Command.PreviousView); - KeyBindings.Add (Key.Tab.WithCtrl, Command.NextViewOrTop); - KeyBindings.Add (Key.Tab.WithShift.WithCtrl, Command.PreviousViewOrTop); + // This should not be here, but in base + foreach (View view in Subviews) + { + if (view.Frame.IntersectsWith (Bounds) && !OutsideTopFrame (this)) + { + //view.SetNeedsLayout (); + view.SetNeedsDisplay (view.Bounds); + view.SetSubViewNeedsDisplay (); + } + } - KeyBindings.Add (Key.F5, Command.Refresh); - KeyBindings.Add (Application.AlternateForwardKey, Command.NextViewOrTop); // Needed on Unix - KeyBindings.Add (Application.AlternateBackwardKey, Command.PreviousViewOrTop); // Needed on Unix + base.OnDrawContent (contentArea); + + // This is causing the menus drawn incorrectly if UseSubMenusSingleFrame is true + //if (this.MenuBar != null && this.MenuBar.IsMenuOpen && this.MenuBar.openMenu != null) { + // // TODO: Hack until we can get compositing working right. + // this.MenuBar.openMenu.Redraw (this.MenuBar.openMenu.Bounds); + //} + } + } + + /// + public override bool OnEnter (View view) { return MostFocused?.OnEnter (view) ?? base.OnEnter (view); } + + /// + public override bool OnLeave (View view) { return MostFocused?.OnLeave (view) ?? base.OnLeave (view); } + + /// + /// Called from before the redraws for + /// the first time. + /// + public virtual void OnLoaded () + { + IsLoaded = true; + + foreach (Toplevel tl in Subviews.Where (v => v is Toplevel)) + { + tl.OnLoaded (); + } + + Loaded?.Invoke (this, EventArgs.Empty); + } + + /// + /// Virtual method to invoke the event. + /// + /// + public virtual void OnQuitKeyChanged (KeyChangedEventArgs e) + { + KeyBindings.Replace (e.OldKey, e.NewKey); + QuitKeyChanged?.Invoke (this, e); + } + + /// + public override void PositionCursor () + { + if (!IsOverlappedContainer) + { + base.PositionCursor (); + + if (Focused == null) + { + EnsureFocus (); + + if (Focused == null) + { + Driver.SetCursorVisibility (CursorVisibility.Invisible); + } + } + + return; + } + + if (Focused == null) + { + foreach (Toplevel top in Application.OverlappedChildren) + { + if (top != this && top.Visible) + { + top.SetFocus (); + + return; + } + } + } + + base.PositionCursor (); + + if (Focused == null) + { + Driver.SetCursorVisibility (CursorVisibility.Invisible); + } + } + + /// + /// Adjusts the location and size of within this Toplevel. + /// Virtual method enabling implementation of specific positions for inherited + /// views. + /// + /// The Toplevel to adjust. + public virtual void PositionToplevel (Toplevel top) + { + View superView = GetLocationThatFits ( + top, + top.Frame.X, + top.Frame.Y, + out int nx, + out int ny, + out _, + out StatusBar sb); + var layoutSubviews = false; + var maxWidth = 0; + + if (superView.Margin != null && superView == top.SuperView) + { + maxWidth -= superView.GetAdornmentsThickness ().Left + superView.GetAdornmentsThickness ().Right; + } + + if ((superView != top || top?.SuperView != null || (top != Application.Top && top.Modal) || (top?.SuperView == null && top.IsOverlapped)) + + // BUGBUG: Prevously PositionToplevel required LayotuStyle.Computed + && (top.Frame.X + top.Frame.Width > maxWidth || ny > top.Frame.Y) /*&& top.LayoutStyle == LayoutStyle.Computed*/) + { + if ((top.X == null || top.X is Pos.PosAbsolute) && top.Frame.X != nx) + { + top.X = nx; + layoutSubviews = true; + } + + if ((top.Y == null || top.Y is Pos.PosAbsolute) && top.Frame.Y != ny) + { + top.Y = ny; + layoutSubviews = true; + } + } + + // TODO: v2 - This is a hack to get the StatusBar to be positioned correctly. + if (sb != null + && !top.Subviews.Contains (sb) + && ny + top.Frame.Height != superView.Frame.Height - (sb.Visible ? 1 : 0) + && top.Height is Dim.DimFill + && -top.Height.Anchor (0) < 1) + { + top.Height = Dim.Fill (sb.Visible ? 1 : 0); + layoutSubviews = true; + } + + if (superView.LayoutNeeded || layoutSubviews) + { + superView.LayoutSubviews (); + } + + if (LayoutNeeded) + { + LayoutSubviews (); + } + } + + /// + /// Invoked when the is changed. + /// + public event EventHandler QuitKeyChanged; + + /// + /// Invoked when the main loop has started it's first iteration. + /// Subscribe to this event to perform tasks when the has been laid out and + /// focus has been set. + /// changes. + /// + /// A Ready event handler is a good place to finalize initialization after calling + /// on this . + /// + /// + public event EventHandler Ready; + + /// + public override void Remove (View view) + { + if (this is Toplevel Toplevel && Toplevel.MenuBar != null) + { + RemoveMenuStatusBar (view); + } + + base.Remove (view); + } + + /// + public override void RemoveAll () + { + if (this == Application.Top) + { + MenuBar?.Dispose (); + MenuBar = null; + StatusBar?.Dispose (); + StatusBar = null; + } + + base.RemoveAll (); + } + + /// + /// Stops and closes this . If this Toplevel is the top-most Toplevel, + /// will be called, causing the application to exit. + /// + public virtual void RequestStop () + { + if (IsOverlappedContainer + && Running + && (Application.Current == this + || Application.Current?.Modal == false + || (Application.Current?.Modal == true && Application.Current?.Running == false))) + { + foreach (Toplevel child in Application.OverlappedChildren) + { + var ev = new ToplevelClosingEventArgs (this); + + if (child.OnClosing (ev)) + { + return; + } + + child.Running = false; + Application.RequestStop (child); + } + + Running = false; + Application.RequestStop (this); + } + else if (IsOverlappedContainer && Running && Application.Current?.Modal == true && Application.Current?.Running == true) + { + var ev = new ToplevelClosingEventArgs (Application.Current); + + if (OnClosing (ev)) + { + return; + } + + Application.RequestStop (Application.Current); + } + else if (!IsOverlappedContainer && Running && (!Modal || (Modal && Application.Current != this))) + { + var ev = new ToplevelClosingEventArgs (this); + + if (OnClosing (ev)) + { + return; + } + + Running = false; + Application.RequestStop (this); + } + else + { + Application.RequestStop (Application.Current); + } + } + + /// + /// Stops and closes the specified by . If + /// is the top-most Toplevel, + /// will be called, causing the application to exit. + /// + /// The Toplevel to request stop. + public virtual void RequestStop (Toplevel top) { top.RequestStop (); } + + /// + /// Invoked when the terminal has been resized. The new of the terminal is provided. + /// + public event EventHandler SizeChanging; + + /// + /// Invoked when the Toplevel has been unloaded. + /// A Unloaded event handler is a good place to dispose objects after calling + /// . + /// + public event EventHandler Unloaded; + + /// + protected override void Dispose (bool disposing) + { + Application.GrabbingMouse -= Application_GrabbingMouse; + Application.UnGrabbingMouse -= Application_UnGrabbingMouse; + + _dragPosition = null; + base.Dispose (disposing); + } + + internal void AddMenuStatusBar (View view) + { + if (view is MenuBar) + { + MenuBar = view as MenuBar; + } + + if (view is StatusBar) + { + StatusBar = view as StatusBar; + } + } + + /// + /// Gets a new location of the that is within the Bounds of the + /// 's + /// (e.g. for dragging a Window). + /// The `out` parameters are the new X and Y coordinates. + /// + /// + /// If does not have a or it's SuperView is not + /// + /// the position will be bound by the and + /// . + /// + /// The Toplevel that is to be moved. + /// The target x location. + /// The target y location. + /// The x location that will ensure will be visible. + /// The y location that will ensure will be visible. + /// The new top most menuBar + /// The new top most statusBar + /// + /// Either (if does not have a Super View) or + /// 's SuperView. This can be used to ensure LayoutSubviews is called on the + /// correct View. + /// + internal View GetLocationThatFits ( + Toplevel top, + int targetX, + int targetY, + out int nx, + out int ny, + out MenuBar menuBar, + out StatusBar statusBar + ) + { + int maxWidth; + View superView; + + if (top?.SuperView == null || top == Application.Top || top?.SuperView == Application.Top) + { + maxWidth = Driver.Cols; + superView = Application.Top; + } + else + { + // Use the SuperView's Bounds, not Frame + maxWidth = top.SuperView.Bounds.Width; + superView = top.SuperView; + } + + if (superView.Margin != null && superView == top.SuperView) + { + maxWidth -= superView.GetAdornmentsThickness ().Left + superView.GetAdornmentsThickness ().Right; + } + + if (top.Frame.Width <= maxWidth) + { + nx = Math.Max (targetX, 0); + nx = nx + top.Frame.Width > maxWidth ? Math.Max (maxWidth - top.Frame.Width, 0) : nx; + + if (nx > top.Frame.X + top.Frame.Width) + { + nx = Math.Max (top.Frame.Right, 0); + } + } + else + { + nx = targetX; + } + + //System.Diagnostics.Debug.WriteLine ($"nx:{nx}, rWidth:{rWidth}"); + bool menuVisible, statusVisible; + + if (top?.SuperView == null || top == Application.Top || top?.SuperView == Application.Top) + { + menuVisible = Application.Top.MenuBar?.Visible == true; + menuBar = Application.Top.MenuBar; + } + else + { + View t = top.SuperView; + + while (t is not Toplevel) + { + t = t.SuperView; + } + + menuVisible = ((Toplevel)t).MenuBar?.Visible == true; + menuBar = ((Toplevel)t).MenuBar; + } + + if (top?.SuperView == null || top == Application.Top || top?.SuperView == Application.Top) + { + maxWidth = menuVisible ? 1 : 0; + } + else + { + maxWidth = 0; + } + + ny = Math.Max (targetY, maxWidth); + + if (top?.SuperView == null || top == Application.Top || top?.SuperView == Application.Top) + { + statusVisible = Application.Top.StatusBar?.Visible == true; + statusBar = Application.Top.StatusBar; + } + else + { + View t = top.SuperView; + + while (t is not Toplevel) + { + t = t.SuperView; + } + + statusVisible = ((Toplevel)t).StatusBar?.Visible == true; + statusBar = ((Toplevel)t).StatusBar; + } + + if (top?.SuperView == null || top == Application.Top || top?.SuperView == Application.Top) + { + maxWidth = statusVisible ? Driver.Rows - 1 : Driver.Rows; + } + else + { + maxWidth = statusVisible ? top.SuperView.Frame.Height - 1 : top.SuperView.Frame.Height; + } + + if (superView.Margin != null && superView == top.SuperView) + { + maxWidth -= superView.GetAdornmentsThickness ().Top + superView.GetAdornmentsThickness ().Bottom; + } + + ny = Math.Min (ny, maxWidth); + + if (top.Frame.Height <= maxWidth) + { + ny = ny + top.Frame.Height > maxWidth ? Math.Max (maxWidth - top.Frame.Height, menuVisible ? 1 : 0) : ny; + + if (ny > top.Frame.Y + top.Frame.Height) + { + ny = Math.Max (top.Frame.Bottom, 0); + } + } + + //System.Diagnostics.Debug.WriteLine ($"ny:{ny}, rHeight:{rHeight}"); + + return superView; + } + + internal virtual void OnActivate (Toplevel deactivated) { Activate?.Invoke (this, new ToplevelEventArgs (deactivated)); } + + internal virtual void OnAllChildClosed () { AllChildClosed?.Invoke (this, EventArgs.Empty); } + + internal virtual void OnChildClosed (Toplevel top) + { + if (IsOverlappedContainer) + { + SetSubViewNeedsDisplay (); + } + + ChildClosed?.Invoke (this, new ToplevelEventArgs (top)); + } + + internal virtual void OnChildLoaded (Toplevel top) { ChildLoaded?.Invoke (this, new ToplevelEventArgs (top)); } + + internal virtual void OnChildUnloaded (Toplevel top) { ChildUnloaded?.Invoke (this, new ToplevelEventArgs (top)); } + + internal virtual void OnClosed (Toplevel top) { Closed?.Invoke (this, new ToplevelEventArgs (top)); } + + internal virtual bool OnClosing (ToplevelClosingEventArgs ev) + { + Closing?.Invoke (this, ev); + + return ev.Cancel; + } + + internal virtual void OnDeactivate (Toplevel activated) { Deactivate?.Invoke (this, new ToplevelEventArgs (activated)); } + + /// + /// Called from after the has entered the + /// first iteration of the loop. + /// + internal virtual void OnReady () + { + foreach (Toplevel tl in Subviews.Where (v => v is Toplevel)) + { + tl.OnReady (); + } + + Ready?.Invoke (this, EventArgs.Empty); + } + + // TODO: Make cancelable? + internal virtual void OnSizeChanging (SizeChangedEventArgs size) { SizeChanging?.Invoke (this, size); } + + /// + /// Called from before the is disposed. + /// + internal virtual void OnUnloaded () + { + foreach (Toplevel tl in Subviews.Where (v => v is Toplevel)) + { + tl.OnUnloaded (); + } + + Unloaded?.Invoke (this, EventArgs.Empty); + } + + // TODO: v2 - Not sure this is needed anymore. + internal void PositionToplevels () + { + PositionToplevel (this); + + foreach (View top in Subviews) + { + if (top is Toplevel) + { + PositionToplevel ((Toplevel)top); + } + } + } + + internal void RemoveMenuStatusBar (View view) + { + if (view is MenuBar) + { + MenuBar?.Dispose (); + MenuBar = null; + } + + if (view is StatusBar) + { + StatusBar?.Dispose (); + StatusBar = null; + } + } + + private void Application_GrabbingMouse (object sender, GrabMouseEventArgs e) + { + if (Application.MouseGrabView == this && _dragPosition.HasValue) + { + e.Cancel = true; + } + } + + private void Application_UnGrabbingMouse (object sender, GrabMouseEventArgs e) + { + if (Application.MouseGrabView == this && _dragPosition.HasValue) + { + e.Cancel = true; + } + } + + private void FocusNearestView (IEnumerable views, Direction direction) + { + if (views == null) + { + return; + } + + var found = false; + var focusProcessed = false; + var idx = 0; + + foreach (View v in views) + { + if (v == this) + { + found = true; + } + + if (found && v != this) + { + if (direction == Direction.Forward) + { + SuperView?.FocusNext (); + } + else + { + SuperView?.FocusPrev (); + } + + focusProcessed = true; + + if (SuperView.Focused != null && SuperView.Focused != this) + { + return; + } + } + else if (found && !focusProcessed && idx == views.Count () - 1) + { + views.ToList () [0].SetFocus (); + } + + idx++; + } + } + + private View GetDeepestFocusedSubview (View view) + { + if (view == null) + { + return null; + } + + foreach (View v in view.Subviews) + { + if (v.HasFocus) + { + return GetDeepestFocusedSubview (v); + } + } + + return view; + } + + private void MoveNextView () + { + View old = GetDeepestFocusedSubview (Focused); + + if (!FocusNext ()) + { + FocusNext (); + } + + if (old != Focused && old != Focused?.Focused) + { + old?.SetNeedsDisplay (); + Focused?.SetNeedsDisplay (); + } + else + { + FocusNearestView (SuperView?.TabIndexes, Direction.Forward); + } + } + + private void MoveNextViewOrTop () + { + if (Application.OverlappedTop == null) + { + Toplevel top = Modal ? this : Application.Top; + top.FocusNext (); + + if (top.Focused == null) + { + top.FocusNext (); + } + + top.SetNeedsDisplay (); + Application.BringOverlappedTopToFront (); + } + else + { + Application.OverlappedMoveNext (); + } + } + + private void MovePreviousView () + { + View old = GetDeepestFocusedSubview (Focused); + + if (!FocusPrev ()) + { + FocusPrev (); + } + + if (old != Focused && old != Focused?.Focused) + { + old?.SetNeedsDisplay (); + Focused?.SetNeedsDisplay (); + } + else + { + FocusNearestView (SuperView?.TabIndexes?.Reverse (), Direction.Backward); + } + } + + private void MovePreviousViewOrTop () + { + if (Application.OverlappedTop == null) + { + Toplevel top = Modal ? this : Application.Top; + top.FocusPrev (); + + if (top.Focused == null) + { + top.FocusPrev (); + } + + top.SetNeedsDisplay (); + Application.BringOverlappedTopToFront (); + } + else + { + Application.OverlappedMovePrevious (); + } + } + + private bool OutsideTopFrame (Toplevel top) + { + if (top.Frame.X > Driver.Cols || top.Frame.Y > Driver.Rows) + { + return true; + } + + return false; + } + + private void QuitToplevel () + { + if (Application.OverlappedTop != null) + { + Application.OverlappedTop.RequestStop (); + } + else + { + Application.RequestStop (); + } + } + + private void SetInitialProperties () + { + ColorScheme = Colors.ColorSchemes ["TopLevel"]; + + Application.GrabbingMouse += Application_GrabbingMouse; + Application.UnGrabbingMouse += Application_UnGrabbingMouse; + + // TODO: v2 - ALL Views (Responders??!?!) should support the commands related to + // - Focus + // Move the appropriate AddCommand calls to `Responder` + + // Things this view knows how to do + AddCommand ( + Command.QuitToplevel, + () => + { + QuitToplevel (); + + return true; + }); + + AddCommand ( + Command.Suspend, + () => + { + Driver.Suspend (); + ; + + return true; + }); + + AddCommand ( + Command.NextView, + () => + { + MoveNextView (); + + return true; + }); + + AddCommand ( + Command.PreviousView, + () => + { + MovePreviousView (); + + return true; + }); + + AddCommand ( + Command.NextViewOrTop, + () => + { + MoveNextViewOrTop (); + + return true; + }); + + AddCommand ( + Command.PreviousViewOrTop, + () => + { + MovePreviousViewOrTop (); + + return true; + }); + + AddCommand ( + Command.Refresh, + () => + { + Application.Refresh (); + + return true; + }); + + // Default keybindings for this view + KeyBindings.Add (Application.QuitKey, Command.QuitToplevel); + + KeyBindings.Add (Key.CursorRight, Command.NextView); + KeyBindings.Add (Key.CursorDown, Command.NextView); + KeyBindings.Add (Key.CursorLeft, Command.PreviousView); + KeyBindings.Add (Key.CursorUp, Command.PreviousView); + + KeyBindings.Add (Key.Tab, Command.NextView); + KeyBindings.Add (Key.Tab.WithShift, Command.PreviousView); + KeyBindings.Add (Key.Tab.WithCtrl, Command.NextViewOrTop); + KeyBindings.Add (Key.Tab.WithShift.WithCtrl, Command.PreviousViewOrTop); + + KeyBindings.Add (Key.F5, Command.Refresh); + KeyBindings.Add (Application.AlternateForwardKey, Command.NextViewOrTop); // Needed on Unix + KeyBindings.Add (Application.AlternateBackwardKey, Command.PreviousViewOrTop); // Needed on Unix #if UNIX_KEY_BINDINGS KeyBindings.Add (Key.Z.WithCtrl, Command.Suspend); @@ -316,680 +1138,102 @@ public partial class Toplevel : View { KeyBindings.Add (Key.I.WithCtrl, Command.NextView); // Unix KeyBindings.Add (Key.B.WithCtrl, Command.PreviousView);// Unix #endif - } - - /// - /// Invoked when the is changed. - /// - public event EventHandler AlternateForwardKeyChanged; - - /// - /// Virtual method to invoke the event. - /// - /// - public virtual void OnAlternateForwardKeyChanged (KeyChangedEventArgs e) - { - KeyBindings.Replace (e.OldKey, e.NewKey); - AlternateForwardKeyChanged?.Invoke (this, e); - } - - /// - /// Invoked when the is changed. - /// - public event EventHandler AlternateBackwardKeyChanged; - - /// - /// Virtual method to invoke the event. - /// - /// - public virtual void OnAlternateBackwardKeyChanged (KeyChangedEventArgs e) - { - KeyBindings.Replace (e.OldKey, e.NewKey); - AlternateBackwardKeyChanged?.Invoke (this, e); - } - - /// - /// Invoked when the is changed. - /// - public event EventHandler QuitKeyChanged; - - /// - /// Virtual method to invoke the event. - /// - /// - public virtual void OnQuitKeyChanged (KeyChangedEventArgs e) - { - KeyBindings.Replace (e.OldKey, e.NewKey); - QuitKeyChanged?.Invoke (this, e); - } - - void MovePreviousViewOrTop () - { - if (Application.OverlappedTop == null) { - var top = Modal ? this : Application.Top; - top.FocusPrev (); - if (top.Focused == null) { - top.FocusPrev (); - } - top.SetNeedsDisplay (); - Application.BringOverlappedTopToFront (); - } else { - Application.OverlappedMovePrevious (); - } - } - - void MoveNextViewOrTop () - { - if (Application.OverlappedTop == null) { - var top = Modal ? this : Application.Top; - top.FocusNext (); - if (top.Focused == null) { - top.FocusNext (); - } - top.SetNeedsDisplay (); - Application.BringOverlappedTopToFront (); - } else { - Application.OverlappedMoveNext (); - } - } - - void MovePreviousView () - { - var old = GetDeepestFocusedSubview (Focused); - if (!FocusPrev ()) { - FocusPrev (); - } - if (old != Focused && old != Focused?.Focused) { - old?.SetNeedsDisplay (); - Focused?.SetNeedsDisplay (); - } else { - FocusNearestView (SuperView?.TabIndexes?.Reverse (), Direction.Backward); - } - } - - void MoveNextView () - { - var old = GetDeepestFocusedSubview (Focused); - if (!FocusNext ()) { - FocusNext (); - } - if (old != Focused && old != Focused?.Focused) { - old?.SetNeedsDisplay (); - Focused?.SetNeedsDisplay (); - } else { - FocusNearestView (SuperView?.TabIndexes, Direction.Forward); - } - } - - void QuitToplevel () - { - if (Application.OverlappedTop != null) { - Application.OverlappedTop.RequestStop (); - } else { - Application.RequestStop (); - } - } - - View GetDeepestFocusedSubview (View view) - { - if (view == null) { - return null; - } - - foreach (var v in view.Subviews) { - if (v.HasFocus) { - return GetDeepestFocusedSubview (v); - } - } - return view; - } - - void FocusNearestView (IEnumerable views, Direction direction) - { - if (views == null) { - return; - } - - var found = false; - var focusProcessed = false; - var idx = 0; - - foreach (var v in views) { - if (v == this) { - found = true; - } - if (found && v != this) { - if (direction == Direction.Forward) { - SuperView?.FocusNext (); - } else { - SuperView?.FocusPrev (); - } - focusProcessed = true; - if (SuperView.Focused != null && SuperView.Focused != this) { - return; - } - } else if (found && !focusProcessed && idx == views.Count () - 1) { - views.ToList () [0].SetFocus (); - } - idx++; - } - } - - /// - public override void Add (View view) - { - CanFocus = true; - AddMenuStatusBar (view); - base.Add (view); - } - - internal void AddMenuStatusBar (View view) - { - if (view is MenuBar) { - MenuBar = view as MenuBar; - } - if (view is StatusBar) { - StatusBar = view as StatusBar; - } - } - - /// - public override void Remove (View view) - { - if (this is Toplevel Toplevel && Toplevel.MenuBar != null) { - RemoveMenuStatusBar (view); - } - base.Remove (view); - } - - /// - public override void RemoveAll () - { - if (this == Application.Top) { - MenuBar?.Dispose (); - MenuBar = null; - StatusBar?.Dispose (); - StatusBar = null; - } - base.RemoveAll (); - } - - internal void RemoveMenuStatusBar (View view) - { - if (view is MenuBar) { - MenuBar?.Dispose (); - MenuBar = null; - } - if (view is StatusBar) { - StatusBar?.Dispose (); - StatusBar = null; - } - } - - /// - /// Gets a new location of the that is within the Bounds of the - /// 's - /// (e.g. for dragging a Window). - /// The `out` parameters are the new X and Y coordinates. - /// - /// - /// If does not have a or it's SuperView is not - /// - /// the position will be bound by the and - /// . - /// - /// The Toplevel that is to be moved. - /// The target x location. - /// The target y location. - /// The x location that will ensure will be visible. - /// The y location that will ensure will be visible. - /// The new top most menuBar - /// The new top most statusBar - /// - /// Either (if does not have a Super View) or - /// 's SuperView. This can be used to ensure LayoutSubviews is called on the - /// correct View. - /// - internal View GetLocationThatFits (Toplevel top, - int targetX, - int targetY, - out int nx, - out int ny, - out MenuBar menuBar, - out StatusBar statusBar) - { - int maxWidth; - View superView; - if (top?.SuperView == null || top == Application.Top || top?.SuperView == Application.Top) { - maxWidth = Driver.Cols; - superView = Application.Top; - } else { - // Use the SuperView's Bounds, not Frame - maxWidth = top.SuperView.Bounds.Width; - superView = top.SuperView; - } - if (superView.Margin != null && superView == top.SuperView) { - maxWidth -= superView.GetAdornmentsThickness ().Left + superView.GetAdornmentsThickness ().Right; - } - if (top.Frame.Width <= maxWidth) { - nx = Math.Max (targetX, 0); - nx = nx + top.Frame.Width > maxWidth ? Math.Max (maxWidth - top.Frame.Width, 0) : nx; - if (nx > top.Frame.X + top.Frame.Width) { - nx = Math.Max (top.Frame.Right, 0); - } - } else { - nx = targetX; - } - //System.Diagnostics.Debug.WriteLine ($"nx:{nx}, rWidth:{rWidth}"); - bool menuVisible, statusVisible; - if (top?.SuperView == null || top == Application.Top || top?.SuperView == Application.Top) { - menuVisible = Application.Top.MenuBar?.Visible == true; - menuBar = Application.Top.MenuBar; - } else { - var t = top.SuperView; - while (t is not Toplevel) { - t = t.SuperView; - } - menuVisible = ((Toplevel)t).MenuBar?.Visible == true; - menuBar = ((Toplevel)t).MenuBar; - } - if (top?.SuperView == null || top == Application.Top || top?.SuperView == Application.Top) { - maxWidth = menuVisible ? 1 : 0; - } else { - maxWidth = 0; - } - ny = Math.Max (targetY, maxWidth); - if (top?.SuperView == null || top == Application.Top || top?.SuperView == Application.Top) { - statusVisible = Application.Top.StatusBar?.Visible == true; - statusBar = Application.Top.StatusBar; - } else { - var t = top.SuperView; - while (t is not Toplevel) { - t = t.SuperView; - } - statusVisible = ((Toplevel)t).StatusBar?.Visible == true; - statusBar = ((Toplevel)t).StatusBar; - } - if (top?.SuperView == null || top == Application.Top || top?.SuperView == Application.Top) { - maxWidth = statusVisible ? Driver.Rows - 1 : Driver.Rows; - } else { - maxWidth = statusVisible ? top.SuperView.Frame.Height - 1 : top.SuperView.Frame.Height; - } - if (superView.Margin != null && superView == top.SuperView) { - maxWidth -= superView.GetAdornmentsThickness ().Top + superView.GetAdornmentsThickness ().Bottom; - } - ny = Math.Min (ny, maxWidth); - if (top.Frame.Height <= maxWidth) { - ny = ny + top.Frame.Height > maxWidth ? Math.Max (maxWidth - top.Frame.Height, menuVisible ? 1 : 0) : ny; - if (ny > top.Frame.Y + top.Frame.Height) { - ny = Math.Max (top.Frame.Bottom, 0); - } - } - //System.Diagnostics.Debug.WriteLine ($"ny:{ny}, rHeight:{rHeight}"); - - return superView; - } - - // TODO: v2 - Not sure this is needed anymore. - internal void PositionToplevels () - { - PositionToplevel (this); - foreach (var top in Subviews) { - if (top is Toplevel) { - PositionToplevel ((Toplevel)top); - } - } - } - - /// - /// Adjusts the location and size of within this Toplevel. - /// Virtual method enabling implementation of specific positions for inherited - /// views. - /// - /// The Toplevel to adjust. - public virtual void PositionToplevel (Toplevel top) - { - var superView = GetLocationThatFits (top, top.Frame.X, top.Frame.Y, - out var nx, out var ny, out _, out var sb); - var layoutSubviews = false; - var maxWidth = 0; - if (superView.Margin != null && superView == top.SuperView) { - maxWidth -= superView.GetAdornmentsThickness ().Left + superView.GetAdornmentsThickness ().Right; - } - if ((superView != top || top?.SuperView != null || top != Application.Top && top.Modal || top?.SuperView == null && top.IsOverlapped) - // BUGBUG: Prevously PositionToplevel required LayotuStyle.Computed - && - (top.Frame.X + top.Frame.Width > maxWidth || ny > top.Frame.Y) /*&& top.LayoutStyle == LayoutStyle.Computed*/) { - - if ((top.X == null || top.X is Pos.PosAbsolute) && top.Frame.X != nx) { - top.X = nx; - layoutSubviews = true; - } - if ((top.Y == null || top.Y is Pos.PosAbsolute) && top.Frame.Y != ny) { - top.Y = ny; - layoutSubviews = true; - } - } - - // TODO: v2 - This is a hack to get the StatusBar to be positioned correctly. - if (sb != null && !top.Subviews.Contains (sb) && ny + top.Frame.Height != superView.Frame.Height - (sb.Visible ? 1 : 0) && top.Height is Dim.DimFill && -top.Height.Anchor (0) < 1) { - - top.Height = Dim.Fill (sb.Visible ? 1 : 0); - layoutSubviews = true; - } - - if (superView.LayoutNeeded || layoutSubviews) { - superView.LayoutSubviews (); - } - if (LayoutNeeded) { - LayoutSubviews (); - } - } - - /// - public override void OnDrawContent (Rect contentArea) - { - if (!Visible) { - return; - } - - if (NeedsDisplay || SubViewNeedsDisplay || LayoutNeeded) { - //Driver.SetAttribute (GetNormalColor ()); - // TODO: It's bad practice for views to always clear. Defeats the purpose of clipping etc... - Clear (); - LayoutSubviews (); - PositionToplevels (); - - if (this == Application.OverlappedTop) { - foreach (var top in Application.OverlappedChildren.AsEnumerable ().Reverse ()) { - if (top.Frame.IntersectsWith (Bounds)) { - if (top != this && !top.IsCurrentTop && !OutsideTopFrame (top) && top.Visible) { - top.SetNeedsLayout (); - top.SetNeedsDisplay (top.Bounds); - top.Draw (); - top.OnRenderLineCanvas (); - } - } - } - } - - // This should not be here, but in base - foreach (var view in Subviews) { - if (view.Frame.IntersectsWith (Bounds) && !OutsideTopFrame (this)) { - //view.SetNeedsLayout (); - view.SetNeedsDisplay (view.Bounds); - view.SetSubViewNeedsDisplay (); - } - } - - base.OnDrawContent (contentArea); - - // This is causing the menus drawn incorrectly if UseSubMenusSingleFrame is true - //if (this.MenuBar != null && this.MenuBar.IsMenuOpen && this.MenuBar.openMenu != null) { - // // TODO: Hack until we can get compositing working right. - // this.MenuBar.openMenu.Redraw (this.MenuBar.openMenu.Bounds); - //} - } - } - - bool OutsideTopFrame (Toplevel top) - { - if (top.Frame.X > Driver.Cols || top.Frame.Y > Driver.Rows) { - return true; - } - return false; - } - - internal static Point? _dragPosition; - Point _startGrabPoint; - - void Application_UnGrabbingMouse (object sender, GrabMouseEventArgs e) - { - if (Application.MouseGrabView == this && _dragPosition.HasValue) { - e.Cancel = true; - } - } - - void Application_GrabbingMouse (object sender, GrabMouseEventArgs e) - { - if (Application.MouseGrabView == this && _dragPosition.HasValue) { - e.Cancel = true; - } - } - - /// - public override bool MouseEvent (MouseEvent mouseEvent) - { - if (!CanFocus) { - return true; - } - - //System.Diagnostics.Debug.WriteLine ($"dragPosition before: {dragPosition.HasValue}"); - - int nx, ny; - if (!_dragPosition.HasValue && (mouseEvent.Flags == MouseFlags.Button1Pressed || mouseEvent.Flags == MouseFlags.Button2Pressed || mouseEvent.Flags == MouseFlags.Button3Pressed)) { - - SetFocus (); - Application.BringOverlappedTopToFront (); - - // Only start grabbing if the user clicks on the title bar. - // BUGBUG: Assumes Frame == Border and Title is always at Y == 0 - if (mouseEvent.Y == 0 && mouseEvent.Flags == MouseFlags.Button1Pressed) { - _startGrabPoint = new Point (mouseEvent.X, mouseEvent.Y); - _dragPosition = new Point (); - nx = mouseEvent.X - mouseEvent.OfX; - ny = mouseEvent.Y - mouseEvent.OfY; - _dragPosition = new Point (nx, ny); - Application.GrabMouse (this); - } - - //System.Diagnostics.Debug.WriteLine ($"Starting at {dragPosition}"); - return true; - } - if (mouseEvent.Flags == (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition) || - mouseEvent.Flags == MouseFlags.Button3Pressed) { - if (_dragPosition.HasValue) { - if (SuperView == null) { - // Redraw the entire app window using just our Frame. Since we are - // Application.Top, and our Frame always == our Bounds (Location is always (0,0)) - // our Frame is actually view-relative (which is what Redraw takes). - // We need to pass all the view bounds because since the windows was - // moved around, we don't know exactly what was the affected region. - Application.Top.SetNeedsDisplay (); - } else { - SuperView.SetNeedsDisplay (); - } - // BUGBUG: Assumes Frame == Border? - GetLocationThatFits (this, mouseEvent.X + (SuperView == null ? mouseEvent.OfX - _startGrabPoint.X : Frame.X - _startGrabPoint.X), - mouseEvent.Y + (SuperView == null ? mouseEvent.OfY - _startGrabPoint.Y : Frame.Y - _startGrabPoint.Y), - out nx, out ny, out _, out _); - - _dragPosition = new Point (nx, ny); - X = nx; - Y = ny; - //System.Diagnostics.Debug.WriteLine ($"Drag: nx:{nx},ny:{ny}"); - - SetNeedsDisplay (); - return true; - } - } - - if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Released) && _dragPosition.HasValue) { - _dragPosition = null; - Application.UngrabMouse (); - } - - //System.Diagnostics.Debug.WriteLine ($"dragPosition after: {dragPosition.HasValue}"); - //System.Diagnostics.Debug.WriteLine ($"Toplevel: {mouseEvent}"); - return false; - } - - /// - /// Stops and closes this . If this Toplevel is the top-most Toplevel, - /// will be called, causing the application to exit. - /// - public virtual void RequestStop () - { - if (IsOverlappedContainer && - Running && - (Application.Current == this || Application.Current?.Modal == false || Application.Current?.Modal == true && Application.Current?.Running == false)) { - - foreach (var child in Application.OverlappedChildren) { - var ev = new ToplevelClosingEventArgs (this); - if (child.OnClosing (ev)) { - return; - } - child.Running = false; - Application.RequestStop (child); - } - Running = false; - Application.RequestStop (this); - } else if (IsOverlappedContainer && Running && Application.Current?.Modal == true && Application.Current?.Running == true) { - var ev = new ToplevelClosingEventArgs (Application.Current); - if (OnClosing (ev)) { - return; - } - Application.RequestStop (Application.Current); - } else if (!IsOverlappedContainer && Running && (!Modal || Modal && Application.Current != this)) { - var ev = new ToplevelClosingEventArgs (this); - if (OnClosing (ev)) { - return; - } - Running = false; - Application.RequestStop (this); - } else { - Application.RequestStop (Application.Current); - } - } - - /// - /// Stops and closes the specified by . If - /// is the top-most Toplevel, - /// will be called, causing the application to exit. - /// - /// The Toplevel to request stop. - public virtual void RequestStop (Toplevel top) => top.RequestStop (); - - /// - public override void PositionCursor () - { - if (!IsOverlappedContainer) { - base.PositionCursor (); - if (Focused == null) { - EnsureFocus (); - if (Focused == null) { - Driver.SetCursorVisibility (CursorVisibility.Invisible); - } - } - return; - } - - if (Focused == null) { - foreach (var top in Application.OverlappedChildren) { - if (top != this && top.Visible) { - top.SetFocus (); - return; - } - } - } - base.PositionCursor (); - if (Focused == null) { - Driver.SetCursorVisibility (CursorVisibility.Invisible); - } - } - - /// - public override bool OnEnter (View view) => MostFocused?.OnEnter (view) ?? base.OnEnter (view); - - /// - public override bool OnLeave (View view) => MostFocused?.OnLeave (view) ?? base.OnLeave (view); - - /// - protected override void Dispose (bool disposing) - { - Application.GrabbingMouse -= Application_GrabbingMouse; - Application.UnGrabbingMouse -= Application_UnGrabbingMouse; - - _dragPosition = null; - base.Dispose (disposing); - } + } } /// -/// Implements the for comparing two s -/// used by . +/// Implements the for comparing two s +/// used by . /// -public class ToplevelEqualityComparer : IEqualityComparer { - /// Determines whether the specified objects are equal. - /// The first object of type to compare. - /// The second object of type to compare. - /// - /// if the specified objects are equal; otherwise, . - /// - public bool Equals (Toplevel x, Toplevel y) - { - if (y == null && x == null) { - return true; - } - if (x == null || y == null) { - return false; - } - if (x.Id == y.Id) { - return true; - } - return false; - } +public class ToplevelEqualityComparer : IEqualityComparer +{ + /// Determines whether the specified objects are equal. + /// The first object of type to compare. + /// The second object of type to compare. + /// + /// if the specified objects are equal; otherwise, . + /// + public bool Equals (Toplevel x, Toplevel y) + { + if (y == null && x == null) + { + return true; + } - /// Returns a hash code for the specified object. - /// The for which a hash code is to be returned. - /// A hash code for the specified object. - /// - /// The type of - /// is a reference type and is . - /// - public int GetHashCode (Toplevel obj) - { - if (obj == null) { - throw new ArgumentNullException (); - } + if (x == null || y == null) + { + return false; + } - var hCode = 0; - if (int.TryParse (obj.Id, out var result)) { - hCode = result; - } - return hCode.GetHashCode (); - } + if (x.Id == y.Id) + { + return true; + } + + return false; + } + + /// Returns a hash code for the specified object. + /// The for which a hash code is to be returned. + /// A hash code for the specified object. + /// + /// The type of + /// is a reference type and is . + /// + public int GetHashCode (Toplevel obj) + { + if (obj == null) + { + throw new ArgumentNullException (); + } + + var hCode = 0; + + if (int.TryParse (obj.Id, out int result)) + { + hCode = result; + } + + return hCode.GetHashCode (); + } } /// -/// Implements the to sort the -/// from the if needed. +/// Implements the to sort the +/// from the if needed. /// -public sealed class ToplevelComparer : IComparer { - /// - /// Compares two objects and returns a value indicating whether one is less than, equal to, or - /// greater than the other. - /// - /// The first object to compare. - /// The second object to compare. - /// - /// A signed integer that indicates the relative values of and - /// , as shown in the following table.Value Meaning Less than zero - /// is less than .Zero - /// equals .Greater than zero - /// is greater than . - /// - public int Compare (Toplevel x, Toplevel y) - { - if (ReferenceEquals (x, y)) { - return 0; - } - if (x == null) { - return -1; - } - if (y == null) { - return 1; - } - return string.Compare (x.Id, y.Id); - } -} \ No newline at end of file +public sealed class ToplevelComparer : IComparer +{ + /// + /// Compares two objects and returns a value indicating whether one is less than, equal to, or + /// greater than the other. + /// + /// The first object to compare. + /// The second object to compare. + /// + /// A signed integer that indicates the relative values of and + /// , as shown in the following table.Value Meaning Less than zero + /// is less than .Zero + /// equals .Greater than zero + /// is greater than . + /// + public int Compare (Toplevel x, Toplevel y) + { + if (ReferenceEquals (x, y)) + { + return 0; + } + + if (x == null) + { + return -1; + } + + if (y == null) + { + return 1; + } + + return string.Compare (x.Id, y.Id); + } +} diff --git a/Terminal.sln.DotSettings b/Terminal.sln.DotSettings new file mode 100644 index 000000000..a674682a7 --- /dev/null +++ b/Terminal.sln.DotSettings @@ -0,0 +1,397 @@ + + True + 5000 + 1000 + 3000 + 2000 + SUGGESTION + ERROR + SUGGESTION + WARNING + HINT + SUGGESTION + WARNING + ERROR + ERROR + WARNING + WARNING + WARNING + WARNING + SUGGESTION + SUGGESTION + ERROR + ERROR + WARNING + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + WARNING + SUGGESTION + WARNING + WARNING + WARNING + WARNING + + + + + Built-in: Full Cleanup + True + Required + Required + Required + Required + True + DefaultExpression + EmptyRecursivePattern + NEXT_LINE + NEXT_LINE + True + True + True + False + True + True + True + True + True + True + True + True + True + True + True + True + True + NEXT_LINE + 1 + 1 + 1 + 1 + 1 + + 1 + 1 + 1 + 0 + NEXT_LINE + TOGETHER + True + 4 + Spaces + NEXT_LINE + NEXT_LINE + 1 + 1 + True + 60 + 1 + 8 + 8 + 8 + NEXT_LINE + True + NEVER + NEVER + True + NEVER + + NEVER + True + True + True + True + True + True + True + True + True + True + True + True + True + True + + 4 + NEXT_LINE + False + True + True + CHOP_IF_LONG + True + True + True + False + False + True + CHOP_IF_LONG + CHOP_IF_LONG + CHOP_IF_LONG + 160 + CHOP_IF_LONG + CHOP_IF_LONG + True + OneIndent + OneIndent + 120 + ByFirstAttr + False + False + True + <Patterns xmlns="urn:schemas-jetbrains-com:member-reordering-patterns"> + <TypePattern DisplayName="Non-reorderable types"> + <TypePattern.Match> + <Or> + <And> + <Kind Is="Interface" /> + <Or> + <HasAttribute Name="System.Runtime.InteropServices.InterfaceTypeAttribute" /> + <HasAttribute Name="System.Runtime.InteropServices.ComImport" /> + </Or> + </And> + <Kind Is="Struct" /> + <HasAttribute Name="JetBrains.Annotations.NoReorderAttribute" /> + <HasAttribute Name="JetBrains.Annotations.NoReorder" /> + </Or> + </TypePattern.Match> + </TypePattern> + <TypePattern DisplayName="xUnit.net Test Classes" RemoveRegions="All"> + <TypePattern.Match> + <And> + <Kind Is="Class" /> + <HasMember> + <And> + <Kind Is="Method" /> + <HasAttribute Inherited="True" Name="Xunit.FactAttribute" /> + <HasAttribute Inherited="True" Name="Xunit.TheoryAttribute" /> + </And> + </HasMember> + </And> + </TypePattern.Match> + <Entry DisplayName="Fields"> + <Entry.Match> + <And> + <Kind Is="Field" /> + <Not> + <Static /> + </Not> + </And> + </Entry.Match> + <Entry.SortBy> + <Readonly /> + <Name /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Constructors"> + <Entry.Match> + <Kind Is="Constructor" /> + </Entry.Match> + <Entry.SortBy> + <Static /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Teardown Methods"> + <Entry.Match> + <And> + <Kind Is="Method" /> + <ImplementsInterface Name="System.IDisposable" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="All other members" /> + <Entry DisplayName="Test Methods" Priority="100"> + <Entry.Match> + <And> + <Kind Is="Method" /> + <HasAttribute Name="Xunit.FactAttribute" /> + <HasAttribute Name="Xunit.TheoryAttribute" /> + </And> + </Entry.Match> + <Entry.SortBy> + <Name /> + </Entry.SortBy> + </Entry> + </TypePattern> + <TypePattern DisplayName="NUnit Test Fixtures" RemoveRegions="All"> + <TypePattern.Match> + <And> + <Kind Is="Class" /> + <Or> + <HasAttribute Inherited="True" Name="NUnit.Framework.TestFixtureAttribute" /> + <HasAttribute Inherited="True" Name="NUnit.Framework.TestFixtureSourceAttribute" /> + <HasMember> + <And> + <Kind Is="Method" /> + <HasAttribute Name="NUnit.Framework.TestAttribute" /> + <HasAttribute Name="NUnit.Framework.TestCaseAttribute" /> + <HasAttribute Name="NUnit.Framework.TestCaseSourceAttribute" /> + </And> + </HasMember> + </Or> + </And> + </TypePattern.Match> + <Entry DisplayName="Setup/Teardown Methods"> + <Entry.Match> + <And> + <Kind Is="Method" /> + <Or> + <HasAttribute Inherited="True" Name="NUnit.Framework.SetUpAttribute" /> + <HasAttribute Inherited="True" Name="NUnit.Framework.TearDownAttribute" /> + <HasAttribute Inherited="True" Name="NUnit.Framework.TestFixtureSetUpAttribute" /> + <HasAttribute Inherited="True" Name="NUnit.Framework.TestFixtureTearDownAttribute" /> + <HasAttribute Inherited="True" Name="NUnit.Framework.OneTimeSetUpAttribute" /> + <HasAttribute Inherited="True" Name="NUnit.Framework.OneTimeTearDownAttribute" /> + </Or> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="All other members" /> + <Entry DisplayName="Test Methods" Priority="100"> + <Entry.Match> + <And> + <Kind Is="Method" /> + <HasAttribute Name="NUnit.Framework.TestAttribute" /> + <HasAttribute Name="NUnit.Framework.TestCaseAttribute" /> + <HasAttribute Name="NUnit.Framework.TestCaseSourceAttribute" /> + </And> + </Entry.Match> + <Entry.SortBy> + <Name /> + </Entry.SortBy> + </Entry> + </TypePattern> + <TypePattern DisplayName="Default Pattern"> + <Entry DisplayName="Public Delegates" Priority="100"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Kind Is="Delegate" /> + </And> + </Entry.Match> + <Entry.SortBy> + <Name /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Public Enums" Priority="100"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Kind Is="Enum" /> + </And> + </Entry.Match> + <Entry.SortBy> + <Name /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Static Fields and Constants"> + <Entry.Match> + <Or> + <Kind Is="Constant" /> + <And> + <Kind Is="Field" /> + <Static /> + </And> + </Or> + </Entry.Match> + <Entry.SortBy> + <Kind Is="Member" /> + <Access Is="0" /> + <Name /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Fields"> + <Entry.Match> + <And> + <Kind Is="Field" /> + <Not> + <Static /> + </Not> + </And> + </Entry.Match> + <Entry.SortBy> + <Access Is="0" /> + <Readonly /> + <Name /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Constructors"> + <Entry.Match> + <Kind Is="Constructor" /> + </Entry.Match> + <Entry.SortBy> + <Static /> + <Access Is="0" /> + </Entry.SortBy> + </Entry> + <Property DisplayName="Properties w/ Backing Field" Priority="100"> + <Entry DisplayName="Backing Field" Priority="100"> + <Entry.Match> + <PropertyPart Match="Field" /> + </Entry.Match> + </Entry> + <Entry DisplayName="Property" Priority="100"> + <Entry.Match> + <PropertyPart Match="Property" /> + </Entry.Match> + </Entry> + </Property> + <Entry DisplayName="Properties, Indexers"> + <Entry.Match> + <Or> + <Kind Is="Property" /> + <Kind Is="Indexer" /> + </Or> + </Entry.Match> + <Entry.SortBy> + <Access Is="0" /> + <Name /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Interface Implementations" Priority="100"> + <Entry.Match> + <And> + <Kind Is="Member" /> + <ImplementsInterface /> + </And> + </Entry.Match> + <Entry.SortBy> + <ImplementsInterface Immediate="True" /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="All other members"> + <Entry.SortBy> + <Access Is="0" /> + <Name /> + <Override /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Nested Types"> + <Entry.Match> + <Kind Is="Type" /> + </Entry.Match> + <Entry.SortBy> + <Access Is="0" /> + <Name /> + </Entry.SortBy> + </Entry> + </TypePattern> +</Patterns> + UseVarWhenEvident + UseExplicitType + UseVarWhenEvident + True + False + False + True + True + False + True + True + True + True + diff --git a/UICatalog/Scenarios/Dialogs.cs b/UICatalog/Scenarios/Dialogs.cs index 584bf410b..5e87fe86a 100644 --- a/UICatalog/Scenarios/Dialogs.cs +++ b/UICatalog/Scenarios/Dialogs.cs @@ -1,267 +1,340 @@ -using System.Text; -using System; +using System; using System.Collections.Generic; using Terminal.Gui; -namespace UICatalog.Scenarios { - [ScenarioMetadata (Name: "Dialogs", Description: "Demonstrates how to the Dialog class")] - [ScenarioCategory ("Dialogs")] - public class Dialogs : Scenario { - static int CODE_POINT = '你'; // We know this is a wide char +namespace UICatalog.Scenarios; - public override void Setup () - { - var frame = new FrameView ("Dialog Options") { - X = Pos.Center (), - Y = 1, - Width = Dim.Percent (75), - Height = Dim.Auto () - }; +[ScenarioMetadata ("Dialogs", "Demonstrates how to the Dialog class")] +[ScenarioCategory ("Dialogs")] +public class Dialogs : Scenario +{ + private static readonly int CODE_POINT = '你'; // We know this is a wide char - var label = new Label ("Width:") { - X = 0, - Y = 0, - Width = 15, - Height = 1, - TextAlignment = Terminal.Gui.TextAlignment.Right, - }; - frame.Add (label); + public override void Setup () + { + var frame = new FrameView ("Dialog Options") + { + X = Pos.Center (), + Y = 1, + Width = Dim.Percent (75), + Height = Dim.Auto () + }; - var widthEdit = new TextField ("0") { - X = Pos.Right (label) + 1, - Y = Pos.Top (label), - Width = 5, - Height = 1 - }; - frame.Add (widthEdit); + var label = new Label ("Width:") + { + X = 0, + Y = 0, + Width = 15, + Height = 1, + TextAlignment = TextAlignment.Right + }; + frame.Add (label); - label = new Label ("Height:") { - X = 0, - Y = Pos.Bottom (label), - Width = Dim.Width (label), - Height = 1, - TextAlignment = Terminal.Gui.TextAlignment.Right, - }; - frame.Add (label); + var widthEdit = new TextField ("0") + { + X = Pos.Right (label) + 1, + Y = Pos.Top (label), + Width = 5, + Height = 1 + }; + frame.Add (widthEdit); - var heightEdit = new TextField ("0") { - X = Pos.Right (label) + 1, - Y = Pos.Top (label), - Width = 5, - Height = 1 - }; - frame.Add (heightEdit); + label = new Label ("Height:") + { + X = 0, + Y = Pos.Bottom (label), + Width = Dim.Width (label), + Height = 1, + TextAlignment = TextAlignment.Right + }; + frame.Add (label); - frame.Add (new Label ("If height & width are both 0,") { - X = Pos.Right (widthEdit) + 2, - Y = Pos.Top (widthEdit), - }); - frame.Add (new Label ("the Dialog will size to 80% of container.") { - X = Pos.Right (heightEdit) + 2, - Y = Pos.Top (heightEdit), - }); + var heightEdit = new TextField ("0") + { + X = Pos.Right (label) + 1, + Y = Pos.Top (label), + Width = 5, + Height = 1 + }; + frame.Add (heightEdit); - label = new Label ("Title:") { - X = 0, - Y = Pos.Bottom (label), - Width = Dim.Width (label), - Height = 1, - TextAlignment = Terminal.Gui.TextAlignment.Right, - }; - frame.Add (label); + frame.Add ( + new Label ("If height & width are both 0,") + { + X = Pos.Right (widthEdit) + 2, + Y = Pos.Top (widthEdit) + }); - var titleEdit = new TextField ("Title") { - X = Pos.Right (label) + 1, - Y = Pos.Top (label), - Width = Dim.Fill (), - Height = 1 - }; - frame.Add (titleEdit); + frame.Add ( + new Label ("the Dialog will size to 80% of container.") + { + X = Pos.Right (heightEdit) + 2, + Y = Pos.Top (heightEdit) + }); - label = new Label ("Num Buttons:") { - X = 0, - Y = Pos.Bottom (label), // BUGBUG: if this is Pos.Bottom (titleEdit) the initial LayoutSubviews does not work correctly?!?! - Width = Dim.Width (label), - Height = 1, - TextAlignment = Terminal.Gui.TextAlignment.Right, - }; - frame.Add (label); + label = new Label ("Title:") + { + X = 0, + Y = Pos.Bottom (label), + Width = Dim.Width (label), + Height = 1, + TextAlignment = TextAlignment.Right + }; + frame.Add (label); - var numButtonsEdit = new TextField ("3") { - X = Pos.Right (label) + 1, - Y = Pos.Top (label), - Width = 5, - Height = 1 - }; - frame.Add (numButtonsEdit); + var titleEdit = new TextField ("Title") + { + X = Pos.Right (label) + 1, + Y = Pos.Top (label), + Width = Dim.Fill (), + Height = 1 + }; + frame.Add (titleEdit); - var glyphsNotWords = new CheckBox ($"Add {Char.ConvertFromUtf32 (CODE_POINT)} to button text to stress wide char support", false) { - X = Pos.Left (numButtonsEdit), - Y = Pos.Bottom (label), - TextAlignment = Terminal.Gui.TextAlignment.Right, - }; - frame.Add (glyphsNotWords); + label = new Label ("Num Buttons:") + { + X = 0, + Y = Pos.Bottom (label), // BUGBUG: if this is Pos.Bottom (titleEdit) the initial LayoutSubviews does not work correctly?!?! + Width = Dim.Width (label), + Height = 1, + TextAlignment = TextAlignment.Right + }; + frame.Add (label); - label = new Label ("Button Style:") { - X = 0, - Y = Pos.Bottom (glyphsNotWords), - TextAlignment = Terminal.Gui.TextAlignment.Right - }; - frame.Add (label); + var numButtonsEdit = new TextField ("3") + { + X = Pos.Right (label) + 1, + Y = Pos.Top (label), + Width = 5, + Height = 1 + }; + frame.Add (numButtonsEdit); - var styleRadioGroup = new RadioGroup (new string [] { "_Center", "_Justify", "_Left", "_Right" }) { - X = Pos.Right (label) + 1, - Y = Pos.Top (label), - }; - frame.Add (styleRadioGroup); + var glyphsNotWords = new CheckBox ($"Add {char.ConvertFromUtf32 (CODE_POINT)} to button text to stress wide char support") + { + X = Pos.Left (numButtonsEdit), + Y = Pos.Bottom (label), + TextAlignment = TextAlignment.Right + }; + frame.Add (glyphsNotWords); - frame.ValidatePosDim = true; - void Top_LayoutComplete (object sender, EventArgs args) - { - frame.Height = - widthEdit.Frame.Height + - heightEdit.Frame.Height + - titleEdit.Frame.Height + - numButtonsEdit.Frame.Height + - glyphsNotWords.Frame.Height + - styleRadioGroup.Frame.Height + frame.GetAdornmentsThickness().Vertical; - } - Application.Top.LayoutComplete += Top_LayoutComplete; + label = new Label ("Button Style:") + { + X = 0, + Y = Pos.Bottom (glyphsNotWords), + TextAlignment = TextAlignment.Right + }; + frame.Add (label); - Win.Add (frame); + var styleRadioGroup = new RadioGroup (new [] { "_Center", "_Justify", "_Left", "_Right" }) + { + X = Pos.Right (label) + 1, + Y = Pos.Top (label) + }; + frame.Add (styleRadioGroup); - label = new Label ("Button Pressed:") { - X = Pos.Center (), - Y = Pos.Bottom (frame) + 4, - Height = 1, - TextAlignment = Terminal.Gui.TextAlignment.Right, - }; - Win.Add (label); + frame.ValidatePosDim = true; - var buttonPressedLabel = new Label (" ") { - X = Pos.Center (), - Y = Pos.Bottom (frame) + 5, - Width = 25, - Height = 1, - ColorScheme = Colors.ColorSchemes ["Error"], - }; - // glyphsNotWords - // false:var btnText = new [] { "Zero", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine" }; - // true: var btnText = new [] { "0", "\u2780", "➁", "\u2783", "\u2784", "\u2785", "\u2786", "\u2787", "\u2788", "\u2789" }; - // \u2781 is ➁ dingbats \ufb70 is + void Top_LayoutComplete (object sender, EventArgs args) + { + frame.Height = + widthEdit.Frame.Height + + heightEdit.Frame.Height + + titleEdit.Frame.Height + + numButtonsEdit.Frame.Height + + glyphsNotWords.Frame.Height + + styleRadioGroup.Frame.Height + + frame.GetAdornmentsThickness ().Vertical; + } - var showDialogButton = new Button ("_Show Dialog") { - X = Pos.Center (), - Y = Pos.Bottom (frame) + 2, - IsDefault = true, - }; - showDialogButton.Clicked += (s, e) => { - var dlg = CreateDemoDialog (widthEdit, heightEdit, titleEdit, numButtonsEdit, glyphsNotWords, styleRadioGroup, buttonPressedLabel); - Application.Run (dlg); - }; + Application.Top.LayoutComplete += Top_LayoutComplete; - Win.Add (showDialogButton); + Win.Add (frame); - Win.Add (buttonPressedLabel); - } + label = new Label ("Button Pressed:") + { + X = Pos.Center (), + Y = Pos.Bottom (frame) + 4, + Height = 1, + TextAlignment = TextAlignment.Right + }; + Win.Add (label); - Dialog CreateDemoDialog (TextField widthEdit, TextField heightEdit, TextField titleEdit, TextField numButtonsEdit, CheckBox glyphsNotWords, RadioGroup styleRadioGroup, Label buttonPressedLabel) - { - Dialog dialog = null; - try { + var buttonPressedLabel = new Label (" ") + { + X = Pos.Center (), + Y = Pos.Bottom (frame) + 5, + Width = 25, + Height = 1, + ColorScheme = Colors.ColorSchemes ["Error"] + }; - int width = 0; - int.TryParse (widthEdit.Text, out width); - int height = 0; - int.TryParse (heightEdit.Text, out height); - int numButtons = 3; - int.TryParse (numButtonsEdit.Text, out numButtons); + // glyphsNotWords + // false:var btnText = new [] { "Zero", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine" }; + // true: var btnText = new [] { "0", "\u2780", "➁", "\u2783", "\u2784", "\u2785", "\u2786", "\u2787", "\u2788", "\u2789" }; + // \u2781 is ➁ dingbats \ufb70 is - var buttons = new List